diff --git a/.github/workflows/build-docker-mariadb.yml b/.github/workflows/build-docker-mariadb.yml index 5cbab46e601..dfcbb52f2ed 100644 --- a/.github/workflows/build-docker-mariadb.yml +++ b/.github/workflows/build-docker-mariadb.yml @@ -12,11 +12,11 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' diff --git a/.github/workflows/build-docker-postgresql.yml b/.github/workflows/build-docker-postgresql.yml index 8ff00cfd8ad..ab927908138 100644 --- a/.github/workflows/build-docker-postgresql.yml +++ b/.github/workflows/build-docker-postgresql.yml @@ -12,11 +12,11 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' diff --git a/.github/workflows/build-documentation.yml b/.github/workflows/build-documentation.yml index 1706d7c7682..83982fafed0 100644 --- a/.github/workflows/build-documentation.yml +++ b/.github/workflows/build-documentation.yml @@ -11,22 +11,22 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' cache: gradle - - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 with: node-version: 16 - name: Congfigure vega-cli run: npm i -g vega-cli --unsafe - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 + uses: gradle/wrapper-validation-action@342dbebe7272035434f9baccc29a816ec6dd2c7b - name: Install additional software run: | sudo apt-get update diff --git a/.github/workflows/build-mariadb.yml b/.github/workflows/build-mariadb.yml index 4b1c3d8d979..33b8cda34d6 100644 --- a/.github/workflows/build-mariadb.yml +++ b/.github/workflows/build-mariadb.yml @@ -18,7 +18,7 @@ jobs: options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=5s --health-timeout=2s --health-retries=3 mock-oauth2-server: - image: ghcr.io/navikt/mock-oauth2-server:1.0.0 + image: ghcr.io/navikt/mock-oauth2-server:2.0.0 ports: - 9000:9000 env: @@ -30,22 +30,22 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' cache: gradle - - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 with: node-version: 16 - name: Congfigure vega-cli run: npm i -g vega-cli --unsafe - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 + uses: gradle/wrapper-validation-action@342dbebe7272035434f9baccc29a816ec6dd2c7b - name: Verify MariaDB connection run: | while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do diff --git a/.github/workflows/build-mysql.yml b/.github/workflows/build-mysql.yml index cfcace45caa..658c89c752f 100644 --- a/.github/workflows/build-mysql.yml +++ b/.github/workflows/build-mysql.yml @@ -18,7 +18,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 mock-oauth2-server: - image: ghcr.io/navikt/mock-oauth2-server:1.0.0 + image: ghcr.io/navikt/mock-oauth2-server:2.0.0 ports: - 9000:9000 env: @@ -30,22 +30,22 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' cache: gradle - - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 with: node-version: 16 - name: Congfigure vega-cli run: npm i -g vega-cli --unsafe - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 + uses: gradle/wrapper-validation-action@342dbebe7272035434f9baccc29a816ec6dd2c7b - name: Verify MariaDB connection run: | while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do diff --git a/.github/workflows/build-postgresql.yml b/.github/workflows/build-postgresql.yml index 2bfdcf4cbd0..856175ed75c 100644 --- a/.github/workflows/build-postgresql.yml +++ b/.github/workflows/build-postgresql.yml @@ -10,7 +10,7 @@ jobs: services: postgresql: - image: postgres:15.3 + image: postgres:16.0 ports: - 5432:5432 env: @@ -19,7 +19,7 @@ jobs: options: --health-cmd="pg_isready -q -d postgres -U root" --health-interval=5s --health-timeout=2s --health-retries=3 mock-oauth2-server: - image: ghcr.io/navikt/mock-oauth2-server:1.0.0 + image: ghcr.io/navikt/mock-oauth2-server:2.0.0 ports: - 9000:9000 env: @@ -31,22 +31,22 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' cache: gradle - - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 with: node-version: 16 - name: Congfigure vega-cli run: npm i -g vega-cli --unsafe - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 + uses: gradle/wrapper-validation-action@342dbebe7272035434f9baccc29a816ec6dd2c7b - name: Verify PostgreSQL connection run: | while ! pg_isready -d postgres -U root -h 127.0.0.1 -p 5432 ; do diff --git a/.github/workflows/smoke-activemq.yml b/.github/workflows/smoke-activemq.yml index 5b4a1cc5d63..2880403886c 100644 --- a/.github/workflows/smoke-activemq.yml +++ b/.github/workflows/smoke-activemq.yml @@ -12,11 +12,11 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' @@ -36,4 +36,4 @@ jobs: - name: Check info Worker1 run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/info | wc --chars) > 100 )) - name: Run Smoke Test with Remote COB - run: ./gradlew --no-daemon --console=plain :integration-tests:cleanTest :integration-tests:test --tests "org.apache.fineract.integrationtests.investor.externalassetowner.InitiateExternalAssetOwnerTransferTest" -PcargoDisabled + run: ./gradlew --no-daemon --console=plain :integration-tests:cleanTest :integration-tests:test --tests "org.apache.fineract.integrationtests.investor.externalassetowner.InitiateExternalAssetOwnerTransferTest.saleActiveLoanToExternalAssetOwnerAndBuybackADayLater" -PcargoDisabled diff --git a/.github/workflows/smoke-kafka.yml b/.github/workflows/smoke-kafka.yml index 347f9a79e01..ce8bc912dbd 100644 --- a/.github/workflows/smoke-kafka.yml +++ b/.github/workflows/smoke-kafka.yml @@ -12,11 +12,11 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' @@ -36,4 +36,4 @@ jobs: - name: Check info Worker1 run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/info | wc --chars) > 100 )) - name: Run Smoke Test with Remote COB - run: ./gradlew --no-daemon --console=plain :integration-tests:cleanTest :integration-tests:test --tests "org.apache.fineract.integrationtests.investor.externalassetowner.InitiateExternalAssetOwnerTransferTest" -PcargoDisabled + run: ./gradlew --no-daemon --console=plain :integration-tests:cleanTest :integration-tests:test --tests "org.apache.fineract.integrationtests.investor.externalassetowner.InitiateExternalAssetOwnerTransferTest.saleActiveLoanToExternalAssetOwnerAndBuybackADayLater" -PcargoDisabled diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 802bbee6815..94f7bb962a0 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -20,16 +20,16 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 with: java-version: '17' distribution: 'zulu' cache: gradle - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 + uses: gradle/wrapper-validation-action@342dbebe7272035434f9baccc29a816ec6dd2c7b - name: Sonarqube run: ./gradlew --no-daemon --console=plain -Dsonar.verbose=true -Dsonar.login=$SONAR_TOKEN -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.projectKey=$SONAR_PROJECT_KEY --info --stacktrace sonarqube diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9eb23f95877..ca79a7bdf51 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # stale-issue-message: 'Stale issue message' diff --git a/build.gradle b/build.gradle index 6beb374993a..4d932bdd995 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ buildscript { plugins { id 'me.qoomon.git-versioning' version '6.4.2' id "org.barfuin.gradle.taskinfo" version "2.1.0" - id 'com.adarshr.test-logger' version '3.2.0' + id 'com.adarshr.test-logger' version '4.0.0' id 'com.diffplug.spotless' version '6.19.0' apply false id 'org.nosphere.apache.rat' version '0.8.0' apply false id 'com.github.hierynomus.license' version '0.16.1' apply false @@ -86,7 +86,7 @@ plugins { id 'org.asciidoctor.jvm.gems' version '3.3.2' apply false id 'org.asciidoctor.kindlegen.base' version '3.2.0' apply false id 'com.google.cloud.tools.jib' version '3.3.2' apply false - id 'org.sonarqube' version '4.2.1.3168' + id 'org.sonarqube' version '4.4.1.3373' id 'com.github.andygoossens.modernizer' version '1.8.0' apply false id 'com.github.spotbugs' version '5.0.14' apply false id 'se.thinkcode.cucumber-runner' version '0.0.11' apply false @@ -356,9 +356,9 @@ configure(project.fineractJavaProjects) { // and during an IntelliJ recompilation, it fails //"-Werror", "-Xmaxwarns", - 1500, + "1500", "-Xmaxerrs", - 1500 + "1500" ] // TODO FINERACT-959 (gradually) enable -Xlint:all (see "javac -help -X") @@ -569,6 +569,7 @@ configure(project.fineractJavaProjects) { 'org.mockito:mockito-junit-jupiter', 'org.junit.jupiter:junit-jupiter-api', 'org.junit.jupiter:junit-jupiter-engine', + 'org.junit.jupiter:junit-jupiter-params', 'org.junit.platform:junit-platform-runner', // required to be able to run tests directly under Eclipse, see FINERACT-943 & FINERACT-1021 'org.bouncycastle:bcpkix-jdk15to18', 'org.bouncycastle:bcprov-jdk15to18', diff --git a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle index a0abf723456..fc5c8365a7a 100644 --- a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle +++ b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle @@ -118,7 +118,7 @@ dependencyManagement { dependency 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' dependency 'org.glassfish.jersey.media:jersey-media-multipart:3.1.2' dependency 'org.glassfish.jaxb:jaxb-runtime:2.3.6' // Swagger needs exactly this version - dependency 'org.apache.bval:org.apache.bval.bundle:2.0.6' + dependency 'org.apache.bval:org.apache.bval.bundle:3.0.0' dependency 'joda-time:joda-time:2.12.5' dependency 'io.github.classgraph:classgraph:4.8.160' diff --git a/custom/docker/build.gradle b/custom/docker/build.gradle index 562857b36e4..8938e3d32bb 100644 --- a/custom/docker/build.gradle +++ b/custom/docker/build.gradle @@ -25,6 +25,12 @@ apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.dependencie jib { from { image = 'azul/zulu-openjdk-alpine:17' + platforms { + platform { + architecture = System.getProperty("os.arch").equals("aarch64")?"arm64":"amd64" + os = 'linux' + } + } } to { diff --git a/docker-compose-postgresql-activemq.yml b/docker-compose-postgresql-activemq.yml index 7140e5a3f88..9ddca562935 100644 --- a/docker-compose-postgresql-activemq.yml +++ b/docker-compose-postgresql-activemq.yml @@ -26,7 +26,7 @@ services: - 61616:61616 # Backend service fineractpostgresql: - image: postgres:15.3 + image: postgres:16.0 volumes: - ./fineract-db/docker/postgresql:/docker-entrypoint-initdb.d/:Z,ro restart: always diff --git a/docker-compose-postgresql-kafka-msk.yml b/docker-compose-postgresql-kafka-msk.yml index 6e6cb563981..52e2f07690b 100644 --- a/docker-compose-postgresql-kafka-msk.yml +++ b/docker-compose-postgresql-kafka-msk.yml @@ -21,7 +21,7 @@ version: '3.7' services: # Backend service fineractpostgresql: - image: postgres:15.3 + image: postgres:16.0 volumes: - ./fineract-db/docker/postgresql:/docker-entrypoint-initdb.d/:Z,ro environment: diff --git a/docker-compose-postgresql-kafka.yml b/docker-compose-postgresql-kafka.yml index bfd632d3663..b774a530f58 100644 --- a/docker-compose-postgresql-kafka.yml +++ b/docker-compose-postgresql-kafka.yml @@ -34,7 +34,7 @@ services: - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 # Backend service fineractpostgresql: - image: postgres:15.3 + image: postgres:16.0 volumes: - ./fineract-db/docker/postgresql:/docker-entrypoint-initdb.d/:Z,ro environment: diff --git a/docker-compose-postgresql.yml b/docker-compose-postgresql.yml index 327046fc841..e3df34fc35c 100644 --- a/docker-compose-postgresql.yml +++ b/docker-compose-postgresql.yml @@ -21,7 +21,7 @@ version: '3.7' services: # Backend service fineractpostgresql: - image: postgres:15.3 + image: postgres:16.0 volumes: - ./fineract-db/docker/postgresql:/docker-entrypoint-initdb.d/:Z,ro restart: always diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanSchedulePeriodDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanSchedulePeriodDataV1.avsc index cb6b8492fa1..74967a6fe42 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanSchedulePeriodDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanSchedulePeriodDataV1.avsc @@ -330,6 +330,14 @@ "null", "bigdecimal" ] + }, + { + "default": null, + "name": "downPaymentPeriod", + "type": [ + "null", + "boolean" + ] } ] } diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformServiceJpaRepositoryImpl.java b/fineract-core/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformServiceJpaRepositoryImpl.java index 44541cc4ced..55764af8e77 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/closure/service/GLClosureWritePlatformServiceJpaRepositoryImpl.java @@ -66,15 +66,14 @@ public CommandProcessingResult createGLClosure(final JsonCommand command) { final Office office = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId); // TODO: Get Tenant specific date // ensure closure date is not in the future - final LocalDate todaysDate = DateUtils.getBusinessLocalDate(); final LocalDate closureDate = command.localDateValueOfParameterNamed(GLClosureJsonInputParams.CLOSING_DATE.getValue()); - if (closureDate.isAfter(todaysDate)) { + if (DateUtils.isDateInTheFuture(closureDate)) { throw new GLClosureInvalidException(GlClosureInvalidReason.FUTURE_DATE, closureDate); } // shouldn't be before an existing accounting closure final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(officeId); if (latestGLClosure != null) { - if (latestGLClosure.getClosingDate().isAfter(closureDate)) { + if (DateUtils.isAfter(latestGLClosure.getClosingDate(), closureDate)) { throw new GLClosureInvalidException(GlClosureInvalidReason.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate()); } } @@ -122,7 +121,7 @@ public CommandProcessingResult deleteGLClosure(final Long glClosureId) { **/ final LocalDate closureDate = glClosure.getClosingDate(); final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(glClosure.getOffice().getId()); - if (latestGLClosure.getClosingDate().isAfter(closureDate)) { + if (DateUtils.isAfter(latestGLClosure.getClosingDate(), closureDate)) { throw new GLClosureInvalidDeleteException(latestGLClosure.getOffice().getId(), latestGLClosure.getOffice().getName(), latestGLClosure.getClosingDate()); } diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/domain/TrialBalance.java b/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/domain/TrialBalance.java index b80c51b0f34..757d3777410 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/domain/TrialBalance.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/domain/TrialBalance.java @@ -30,6 +30,7 @@ import lombok.Setter; import lombok.experimental.Accessors; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; @Entity @Table(name = "m_trial_balance") @@ -70,9 +71,8 @@ public boolean equals(Object obj) { } TrialBalance other = (TrialBalance) obj; return Objects.equals(other.officeId, officeId) && Objects.equals(other.glAccountId, glAccountId) - && Objects.equals(other.amount, amount) && other.entryDate.compareTo(entryDate) == 0 ? Boolean.TRUE - : Boolean.FALSE && other.transactionDate.compareTo(transactionDate) == 0 ? Boolean.TRUE - : Boolean.FALSE && Objects.equals(other.closingBalance, closingBalance); + && Objects.equals(other.amount, amount) && DateUtils.isEqual(other.entryDate, entryDate) + && DateUtils.isEqual(other.transactionDate, transactionDate) && Objects.equals(other.closingBalance, closingBalance); } @Override diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java b/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java index c415dc876a1..1899b3ce13a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.batch.command; +import static jakarta.ws.rs.HttpMethod.DELETE; import static jakarta.ws.rs.HttpMethod.GET; import static jakarta.ws.rs.HttpMethod.POST; import static jakarta.ws.rs.HttpMethod.PUT; @@ -176,11 +177,11 @@ private static void init() { commandStrategies.put( CommandContext.resource("v1\\/loans\\/" + NUMBER_REGEX + "\\/transactions\\/" + NUMBER_REGEX + OPTIONAL_COMMAND_PARAM_REGEX) .method(POST).build(), - "adjustTransactionCommandStrategy"); + "adjustLoanTransactionCommandStrategy"); commandStrategies.put( CommandContext.resource("v1\\/loans\\/external-id\\/" + UUID_PARAM_REGEX + "\\/transactions\\/external-id\\/" + UUID_PARAM_REGEX + OPTIONAL_COMMAND_PARAM_REGEX).method(POST).build(), - "adjustTransactionByExternalIdCommandStrategy"); + "adjustLoanTransactionByExternalIdCommandStrategy"); commandStrategies.put(CommandContext.resource("v1\\/clients\\/" + NUMBER_REGEX + "\\?command=activate").method(POST).build(), "activateClientCommandStrategy"); commandStrategies.put(CommandContext.resource("v1\\/loans\\/" + NUMBER_REGEX + "\\?command=approve").method(POST).build(), @@ -195,9 +196,11 @@ private static void init() { "approveLoanRescheduleCommandStrategy"); commandStrategies.put( CommandContext.resource("v1\\/loans\\/" + NUMBER_REGEX + "\\/transactions\\/" + NUMBER_REGEX).method(GET).build(), - "getTransactionByIdCommandStrategy"); - commandStrategies.put(CommandContext.resource("v1\\/loans\\/external-id\\/" + UUID_PARAM_REGEX + "\\/transactions\\/external-id\\/" - + UUID_PARAM_REGEX + OPTIONAL_QUERY_PARAM_REGEX).method(GET).build(), "getTransactionByExternalIdCommandStrategy"); + "getLoanTransactionByIdCommandStrategy"); + commandStrategies.put( + CommandContext.resource("v1\\/loans\\/external-id\\/" + UUID_PARAM_REGEX + "\\/transactions\\/external-id\\/" + + UUID_PARAM_REGEX + OPTIONAL_QUERY_PARAM_REGEX).method(GET).build(), + "getLoanTransactionByExternalIdCommandStrategy"); commandStrategies.put(CommandContext.resource("v1\\/datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX) .method(POST).build(), "createDatatableEntryCommandStrategy"); commandStrategies.put(CommandContext @@ -207,7 +210,9 @@ private static void init() { .method(PUT).build(), "updateDatatableEntryOneToOneCommandStrategy"); commandStrategies.put(CommandContext .resource("v1\\/datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX + "\\/" + NUMBER_REGEX) - .method(PUT).build(), "updateDatatableEntryOneToManyCommandStrategy"); + .method(DELETE).build(), "deleteDatatableEntryOneToManyCommandStrategy"); + commandStrategies.put(CommandContext.resource("v1\\/datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX) + .method(DELETE).build(), "deleteDatatableEntryOneToOneCommandStrategy"); commandStrategies.put(CommandContext .resource("v1\\/datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX + OPTIONAL_QUERY_PARAM_REGEX) .method(GET).build(), "getDatatableEntryByAppTableIdCommandStrategy"); diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/command/internal/UnknownCommandStrategy.java b/fineract-core/src/main/java/org/apache/fineract/batch/command/internal/UnknownCommandStrategy.java index 74f3cec0257..5468254c724 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/command/internal/UnknownCommandStrategy.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/command/internal/UnknownCommandStrategy.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.batch.command.internal; +import static org.apache.http.HttpStatus.SC_NOT_IMPLEMENTED; + import jakarta.ws.rs.core.UriInfo; import org.apache.fineract.batch.command.CommandStrategy; import org.apache.fineract.batch.domain.BatchRequest; @@ -35,15 +37,8 @@ public class UnknownCommandStrategy implements CommandStrategy { @Override public BatchResponse execute(BatchRequest batchRequest, @SuppressWarnings("unused") UriInfo uriInfo) { - - final BatchResponse batchResponse = new BatchResponse(); - - batchResponse.setRequestId(batchRequest.getRequestId()); - batchResponse.setStatusCode(501); - batchResponse.setBody("Resource with method " + batchRequest.getMethod() + " and relativeUrl " + batchRequest.getRelativeUrl() - + " doesn't exist"); - - return batchResponse; + return new BatchResponse().setRequestId(batchRequest.getRequestId()).setStatusCode(SC_NOT_IMPLEMENTED) + .setBody("Resource with method " + batchRequest.getMethod() + " and relativeUrl " + batchRequest.getRelativeUrl() + + " doesn't exist"); } - } diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/exception/ClientDetailsNotFoundException.java b/fineract-core/src/main/java/org/apache/fineract/batch/exception/BatchReferenceInvalidException.java similarity index 73% rename from fineract-core/src/main/java/org/apache/fineract/batch/exception/ClientDetailsNotFoundException.java rename to fineract-core/src/main/java/org/apache/fineract/batch/exception/BatchReferenceInvalidException.java index b0885f5210d..36c945779cf 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/exception/ClientDetailsNotFoundException.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/exception/BatchReferenceInvalidException.java @@ -20,10 +20,13 @@ import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; -public class ClientDetailsNotFoundException extends AbstractPlatformDomainRuleException { +public class BatchReferenceInvalidException extends AbstractPlatformDomainRuleException { - public ClientDetailsNotFoundException() { - super("validation.msg.batch.jlg.no.clients.defined", "No Client details found", ""); + public BatchReferenceInvalidException() { + super("validation.msg.batch.root.invalid", "Root request not found"); } + public BatchReferenceInvalidException(Long reference) { + super("validation.msg.batch.reference.invalid", "Referenced request not found", reference); + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java b/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java index babc57c05d1..0f34911201f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java @@ -21,17 +21,31 @@ import static org.springframework.core.ResolvableType.forClassWithGenerics; import com.google.gson.Gson; +import jakarta.persistence.PersistenceException; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; +import java.text.ParseException; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import javax.naming.AuthenticationException; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import net.fortuna.ical4j.validate.ValidationException; import org.apache.commons.collections4.SetUtils; +import org.apache.fineract.batch.domain.Header; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.exceptionmapper.DefaultExceptionMapper; import org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper; import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.core.NestedRuntimeException; +import org.springframework.dao.NonTransientDataAccessException; import org.springframework.stereotype.Component; /** @@ -55,40 +69,88 @@ public final class ErrorHandler { private static final Gson JSON_HELPER = GoogleGsonSerializerHelper.createGsonBuilder(true).create(); - private ExceptionMapper findMostSpecificExceptionHandler(T exception) { + @NotNull + public ExceptionMapper findMostSpecificExceptionHandler(T exception) { Class clazz = exception.getClass(); do { Set exceptionMappers = createSet(ctx.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class, clazz))); Set fineractErrorMappers = createSet(ctx.getBeanNamesForType(FineractExceptionMapper.class)); SetUtils.SetView intersection = SetUtils.intersection(exceptionMappers, fineractErrorMappers); - if (intersection.size() > 0) { + if (!intersection.isEmpty()) { // noinspection unchecked return (ExceptionMapper) ctx.getBean(intersection.iterator().next()); } + if (!exceptionMappers.isEmpty()) { + // noinspection unchecked + return (ExceptionMapper) ctx.getBean(exceptionMappers.iterator().next()); + } clazz = clazz.getSuperclass(); } while (!clazz.equals(Exception.class)); // noinspection unchecked return (ExceptionMapper) defaultExceptionMapper; } - private Set createSet(T[] array) { - if (array == null) { - return Set.of(); - } else { - return Set.of(array); - } - } - /** * Returns an object of ErrorInfo type containing the information regarding the raised error. * * @param exception * @return ErrorInfo */ - public ErrorInfo handle(final RuntimeException exception) { + public ErrorInfo handle(@NotNull RuntimeException exception) { ExceptionMapper exceptionMapper = findMostSpecificExceptionHandler(exception); Response response = exceptionMapper.toResponse(exception); - return new ErrorInfo(response.getStatus(), ((FineractExceptionMapper) exceptionMapper).errorCode(), - JSON_HELPER.toJson(response.getEntity())); + MultivaluedMap headers = response.getHeaders(); + Set
batchHeaders = headers == null ? null + : headers.keySet().stream().map(e -> new Header(e, response.getHeaderString(e))).collect(Collectors.toSet()); + Integer errorCode = exceptionMapper instanceof FineractExceptionMapper ? ((FineractExceptionMapper) exceptionMapper).errorCode() + : null; + Object msg = response.getEntity(); + return new ErrorInfo(response.getStatus(), errorCode, msg instanceof String ? (String) msg : JSON_HELPER.toJson(msg), batchHeaders); + } + + public RuntimeException getMappable(@NotNull Throwable thr) { + return getMappable(thr, null, null, null); + } + + public RuntimeException getMappable(@NotNull Throwable t, String msgCode, String defaultMsg, String param, + final Object... defaultMsgArgs) { + String msg = defaultMsg == null ? t.getMessage() : defaultMsg; + param = param == null ? "unknown" : param; + if (t instanceof NestedRuntimeException nre) { + Throwable cause = nre.getMostSpecificCause(); + msg = cause.getMessage(); + if (nre instanceof NonTransientDataAccessException) { + msgCode = msgCode == null ? "error.msg." + param + ".data.integrity.issue" : msgCode; + return new PlatformDataIntegrityException(msgCode, msg, param, defaultMsgArgs); + } + } + if (t instanceof ValidationException) { + msgCode = msgCode == null ? "error.msg.validation." + param + ".error" : msgCode; + return new PlatformApiDataValidationException(List.of(ApiParameterError.parameterError(msgCode, msg, param, defaultMsgArgs))); + } + if (t instanceof PersistenceException) { + msgCode = msgCode == null ? "error.msg.persistence." + param + ".error" : msgCode; + return new PlatformDataIntegrityException(msgCode, msg, param, defaultMsgArgs); + } + if (t instanceof AuthenticationException) { + msgCode = msgCode == null ? "error.msg.authentication." + param + ".error" : msgCode; + return new PlatformDataIntegrityException(msgCode, msg, param, defaultMsgArgs); + } + if (t instanceof RuntimeException re) { + return re; + } + if (t instanceof ParseException) { + msgCode = msgCode == null ? "error.msg.parse." + param + ".error" : msgCode; + return new PlatformDataIntegrityException(msgCode, msg, param, defaultMsgArgs); + } + return new RuntimeException(msg, t); + } + + private Set createSet(T[] array) { + if (array == null) { + return Set.of(); + } else { + return Set.of(array); + } } } diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorInfo.java b/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorInfo.java index d3a9a7f9bd0..8512ea1cf98 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorInfo.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorInfo.java @@ -18,6 +18,15 @@ */ package org.apache.fineract.batch.exception; +import static lombok.AccessLevel.PROTECTED; + +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.batch.domain.Header; + /** * Provides members to hold the basic information about the exceptions raised in commandStrategy classes. * @@ -25,84 +34,14 @@ * * @see ErrorHandler */ +@Getter +@Setter(PROTECTED) +@NoArgsConstructor(access = PROTECTED) +@AllArgsConstructor public final class ErrorInfo { private Integer statusCode; private Integer errorCode; private String message; - - /** - * Constructor to initialize the members of this class. - * - * @param statusCode - * @param errorCode - * @param message - */ - public ErrorInfo(final Integer statusCode, final Integer errorCode, final String message) { - - this.statusCode = statusCode; - this.errorCode = errorCode; - this.message = message; - } - - /** - * Constructor so JSON serialization will work with out special Serialiazer - */ - ErrorInfo() { - - } - - /** - * Getter method to provide the statusCode for an object of this type. - * - * @return Integer - */ - public Integer getStatusCode() { - return this.statusCode; - } - - /** - * Setter method to set the statusCode for an object of this type. - * - * @param statusCode - */ - public void setStatusCode(final Integer statusCode) { - this.statusCode = statusCode; - } - - /** - * Getter method to provide the errorCode for an object of this type. - * - * @return Integer - */ - public Integer getErrorCode() { - return this.errorCode; - } - - /** - * Setter method to set the errorCode for an object of this type. - * - * @param errorCode - */ - public void setErrorCode(final Integer errorCode) { - this.errorCode = errorCode; - } - - /** - * Getter method to provide the message of the error for an object of this type. - * - * @return String - */ - public String getMessage() { - return this.message; - } - - /** - * Setter method to set the message of the error for an object of this type. - * - * @param message - */ - public void setMessage(final String message) { - this.message = message; - } + private Set
headers; } diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java index a14a8d9d228..834002fe060 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java @@ -18,23 +18,23 @@ */ package org.apache.fineract.batch.service; -import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.apache.http.HttpStatus.SC_OK; import com.google.gson.Gson; import com.jayway.jsonpath.JsonPathException; import io.github.resilience4j.core.functions.Either; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.UriInfo; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -47,25 +47,25 @@ import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.batch.domain.BatchResponse; import org.apache.fineract.batch.domain.Header; -import org.apache.fineract.batch.exception.ClientDetailsNotFoundException; +import org.apache.fineract.batch.exception.BatchReferenceInvalidException; import org.apache.fineract.batch.exception.ErrorHandler; import org.apache.fineract.batch.exception.ErrorInfo; import org.apache.fineract.batch.service.ResolutionHelper.BatchRequestNode; import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException; -import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException; -import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException; -import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException; import org.apache.fineract.infrastructure.core.filters.BatchCallHandler; import org.apache.fineract.infrastructure.core.filters.BatchFilter; import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor; import org.jetbrains.annotations.NotNull; +import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.NonTransientDataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionExecution; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.support.TransactionTemplate; /** @@ -94,6 +94,64 @@ public class BatchApiServiceImpl implements BatchApiService { @PersistenceContext private final EntityManager entityManager; + /** + * Run each request root step in a separated transaction + * + * @param requestList + * @param uriInfo + * @return + */ + @Override + public List handleBatchRequestsWithoutEnclosingTransaction(final List requestList, UriInfo uriInfo) { + BatchRequestContextHolder.setEnclosingTransaction(Optional.empty()); + return handleBatchRequests(false, requestList, uriInfo); + } + + /** + * Run the batch request in transaction + * + * @param requestList + * @param uriInfo + * @return + */ + @Override + public List handleBatchRequestsWithEnclosingTransaction(final List requestList, final UriInfo uriInfo) { + return callInTransaction(Function.identity()::apply, () -> handleBatchRequests(true, requestList, uriInfo)); + } + + /** + * Helper method to run the command in transaction + * + * @param request + * the enclosing supplier of the command + * @param transactionConfigurator + * consumer to configure the transaction behavior and isolation + * @return + */ + private List callInTransaction(Consumer transactionConfigurator, + Supplier> request) { + List responseList = new ArrayList<>(); + try { + TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); + transactionConfigurator.accept(transactionTemplate); + return transactionTemplate.execute(status -> { + BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status)); + try { + responseList.addAll(request.get()); + return responseList; + } catch (BatchExecutionException ex) { + status.setRollbackOnly(); + return List.of(buildErrorResponse(ex.getCause(), ex.getRequest())); + } catch (RuntimeException ex) { + status.setRollbackOnly(); + return buildErrorResponses(ex, responseList); + } + }); + } catch (TransactionException | NonTransientDataAccessException ex) { + return buildErrorResponses(ex, responseList); + } + } + /** * Returns the response list by getting a proper {@link org.apache.fineract.batch.command.CommandStrategy}. * execute() method of acquired commandStrategy is then provided with the separate Request. @@ -104,29 +162,24 @@ public class BatchApiServiceImpl implements BatchApiService { */ private List handleBatchRequests(boolean enclosingTransaction, final List requestList, final UriInfo uriInfo) { - - final List responseList = new ArrayList<>(requestList.size()); - - final List batchRequestNodes = this.resolutionHelper.getDependingRequests(requestList); - if (batchRequestNodes.isEmpty()) { - final BatchResponse response = new BatchResponse(); - ErrorInfo ex = errorHandler.handle(new ClientDetailsNotFoundException()); - response.setStatusCode(500); - response.setBody(ex.getMessage()); - responseList.add(response); - return responseList; + final List rootNodes; + try { + rootNodes = this.resolutionHelper.buildNodesTree(requestList); + } catch (BatchReferenceInvalidException e) { + return List.of(buildErrorResponse(e)); } - for (BatchRequestNode rootNode : batchRequestNodes) { + final ArrayList responseList = new ArrayList<>(requestList.size()); + for (BatchRequestNode rootNode : rootNodes) { if (enclosingTransaction) { this.callRequestRecursive(rootNode.getRequest(), rootNode, responseList, uriInfo, enclosingTransaction); } else { - List localResponseList = new ArrayList<>(); + ArrayList localResponseList = new ArrayList<>(); this.callRequestRecursive(rootNode.getRequest(), rootNode, localResponseList, uriInfo, enclosingTransaction); responseList.addAll(localResponseList); } } - Collections.sort(responseList, Comparator.comparing(BatchResponse::getRequestId)); + responseList.sort(Comparator.comparing(BatchResponse::getRequestId)); return responseList; } @@ -143,7 +196,7 @@ private List handleBatchRequests(boolean enclosingTransaction, fi */ private void callRequestRecursive(BatchRequest request, BatchRequestNode requestNode, List responseList, UriInfo uriInfo, boolean enclosingTransaction) { - // 1. run current node + // run current node BatchResponse response; if (enclosingTransaction) { response = executeRequest(request, uriInfo); @@ -154,54 +207,28 @@ private void callRequestRecursive(BatchRequest request, BatchRequestNode request response = transactionResponse.get(0); } responseList.add(response); - if (response.getStatusCode() != null && response.getStatusCode() == 200) { - requestNode.getChildRequests().forEach(childNode -> { + if (response.getStatusCode() != null && response.getStatusCode() == SC_OK) { + // run child nodes + requestNode.getChildNodes().forEach(childNode -> { + BatchRequest childRequest = childNode.getRequest(); BatchRequest resolvedChildRequest; try { - resolvedChildRequest = this.resolutionHelper.resoluteRequest(childNode.getRequest(), response); - } catch (JsonPathException jsonPathException) { - BatchResponse childResponse = new BatchResponse().setRequestId(childNode.getRequest().getRequestId()) - .setHeaders(childNode.getRequest().getHeaders()).setStatusCode(BAD_REQUEST.value()) - .setBody(jsonPathException.getMessage()); - responseList.add(childResponse); + resolvedChildRequest = this.resolutionHelper.resolveRequest(childRequest, response); + } catch (JsonPathException jpex) { + responseList.add(buildErrorResponse(jpex, childRequest)); return; } catch (RuntimeException ex) { - throw new BatchExecutionException(childNode.getRequest(), ex, errorHandler.handle(ex)); + throw new BatchExecutionException(childRequest, ex); } callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo, enclosingTransaction); }); } else { - responseList.addAll(parentRequestFailedRecursive(request, requestNode)); + responseList.addAll(parentRequestFailedRecursive(request, requestNode, response, null)); } // If the current request fails, then all the child requests are not executed. If we want to write out all the // child requests, here is the place. } - /** - * All requests recursively are set to status 409 if the parent request fails. - * - * @param request - * the current request - * @param requestNode - * the current request node - * @return {@code BatchResponse} list of the generated batch responses - */ - private List parentRequestFailedRecursive(BatchRequest request, BatchRequestNode requestNode) { - List responseList = new ArrayList<>(); - BatchRequestContextHolder.getEnclosingTransaction().ifPresent(TransactionExecution::setRollbackOnly); - BatchResponse errorResponse = new BatchResponse(); - errorResponse.setRequestId(request.getRequestId()); - errorResponse.setStatusCode(Status.CONFLICT.getStatusCode()); - - // Some detail information about the error - final ErrorInfo conflictError = new ErrorInfo(Status.CONFLICT.getStatusCode(), 8001, - "Parent request with id " + request.getRequestId() + " was erroneous!"); - errorResponse.setBody(conflictError.getMessage()); - requestNode.getChildRequests().stream() - .flatMap(childNode -> parentRequestFailedRecursive(childNode.getRequest(), childNode).stream()).forEach(responseList::add); - return responseList; - } - /** * Execute the request * @@ -215,7 +242,7 @@ private BatchResponse executeRequest(BatchRequest request, UriInfo uriInfo) { log.debug("Batch request: method [{}], relative url [{}]", request.getMethod(), request.getRelativeUrl()); Either preprocessorResult = runPreprocessors(request); if (preprocessorResult.isLeft()) { - throw new BatchExecutionException(request, preprocessorResult.getLeft(), errorHandler.handle(preprocessorResult.getLeft())); + throw new BatchExecutionException(request, preprocessorResult.getLeft()); } else { request = preprocessorResult.get(); } @@ -224,13 +251,8 @@ private BatchResponse executeRequest(BatchRequest request, UriInfo uriInfo) { .map(list -> list.stream().collect(Collectors.toMap(Header::getName, Header::getValue))) .orElse(Collections.emptyMap()))); BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute); - if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) { - if (BatchRequestContextHolder.getEnclosingTransaction().get().isRollbackOnly()) { - BatchResponse br = new BatchResponse(); - br.setBody("Parent request was erroneous!"); - br.setRequestId(request.getRequestId()); - return br; - } + Optional transaction = BatchRequestContextHolder.getEnclosingTransaction(); + if (transaction.isPresent()) { entityManager.flush(); } final BatchResponse rootResponse = callHandler.serviceCall(request, uriInfo); @@ -238,9 +260,9 @@ private BatchResponse executeRequest(BatchRequest request, UriInfo uriInfo) { request.getRelativeUrl()); return rootResponse; } catch (AbstractIdempotentCommandException idempotentException) { - return handleIdempotentRequests(idempotentException, request); + return buildErrorResponse(idempotentException, request); } catch (RuntimeException ex) { - throw new BatchExecutionException(request, ex, errorHandler.handle(ex)); + throw new BatchExecutionException(request, ex); } finally { BatchRequestContextHolder.resetRequestAttributes(); } @@ -266,126 +288,98 @@ private Either runPreprocessor(List())); - response.getHeaders().add(new Header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER, "true")); - response.setBody(idempotentException.getResponse()); - if (idempotentException instanceof IdempotentCommandProcessSucceedException) { - response.setStatusCode(200); - } else if (idempotentException instanceof IdempotentCommandProcessUnderProcessingException) { - response.setStatusCode(409); - } else if (idempotentException instanceof IdempotentCommandProcessFailedException) { - response.setStatusCode(((IdempotentCommandProcessFailedException) idempotentException).getStatusCode()); + private List parentRequestFailedRecursive(@NotNull BatchRequest request, @NotNull BatchRequestNode requestNode, + @NotNull BatchResponse response, Long parentId) { + List responseList = new ArrayList<>(); + if (parentId == null) { // root + BatchRequestContextHolder.getEnclosingTransaction().ifPresent(TransactionExecution::setRollbackOnly); } else { - response.setStatusCode(500); + responseList.add(buildErrorResponse(request.getRequestId(), response.getStatusCode(), + "Parent request with id " + parentId + " was erroneous!", null)); } - return response; + requestNode.getChildNodes().forEach(childNode -> responseList + .addAll(parentRequestFailedRecursive(childNode.getRequest(), childNode, response, request.getRequestId()))); + return responseList; } - /** - * Run each request root step in a separated transaction - * - * @param requestList - * @param uriInfo - * @return - */ - @Override - public List handleBatchRequestsWithoutEnclosingTransaction(final List requestList, UriInfo uriInfo) { - BatchRequestContextHolder.setEnclosingTransaction(Optional.empty()); - return handleBatchRequests(false, requestList, uriInfo); + @NotNull + private BatchResponse buildErrorResponse(@NotNull Throwable ex) { + return buildErrorResponse(ex, null); } /** - * Run the batch request in transaction + * Return the response when any exception raised * - * @param requestList - * @param uriInfo - * @return + * @param ex + * the exception + * @param request + * the called request */ - @Override - public List handleBatchRequestsWithEnclosingTransaction(final List requestList, final UriInfo uriInfo) { - return callInTransaction(() -> handleBatchRequests(true, requestList, uriInfo)); + private BatchResponse buildErrorResponse(Throwable ex, BatchRequest request) { + Long requestId = null; + Integer statusCode = null; + String body = null; + Set
headers = new HashSet<>(); + if (ex != null) { + ErrorInfo errorInfo = errorHandler.handle(errorHandler.getMappable(ex)); + statusCode = errorInfo.getStatusCode(); + body = errorInfo.getMessage(); + headers = Optional.ofNullable(errorInfo.getHeaders()).orElse(new HashSet<>()); + } + if (request != null) { + requestId = request.getRequestId(); + if (request.getHeaders() != null) { + headers.addAll(request.getHeaders()); + } + } + return buildErrorResponse(requestId, statusCode, body, headers); } @NotNull - private List createErrorResponse(List responseList, int statusCode) { - BatchResponse errResponse = new BatchResponse(); + private List buildErrorResponses(Throwable ex, @NotNull List responseList) { + BatchResponse response = responseList.isEmpty() ? null + : responseList.stream().filter(e -> e.getStatusCode() == null || e.getStatusCode() != SC_OK).findFirst() + .orElse(responseList.get(responseList.size() - 1)); - for (BatchResponse res : responseList) { - if (res.getStatusCode() == null || !res.getStatusCode().equals(200)) { - errResponse.setBody("Transaction is being rolled back. First erroneous request: \n" + new Gson().toJson(res)); - errResponse.setRequestId(res.getRequestId()); - if (statusCode == -1) { - if (res.getStatusCode() != null) { - statusCode = res.getStatusCode(); - } else { - statusCode = Status.INTERNAL_SERVER_ERROR.getStatusCode(); - } - } - break; - } + if (response != null && response.getStatusCode() == SC_OK && ex instanceof TransactionSystemException tse) { + ex = new ConcurrencyFailureException(tse.getMessage(), tse.getCause()); } - errResponse.setStatusCode(statusCode); - return Arrays.asList(errResponse); - } - private List callInTransaction(Supplier> request) { - return callInTransaction(Function.identity()::apply, request); - } - - /** - * Helper method to run the command in transaction - * - * @param request - * the enclosing supplier of the command - * @param transactionConfigurator - * consumer to configure the transaction behavior and isolation - * @return - */ - private List callInTransaction(Consumer transactionConfigurator, - Supplier> request) { - List responseList = new ArrayList<>(); - try { - TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); - transactionConfigurator.accept(transactionTemplate); - return transactionTemplate.execute(status -> { - BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status)); - try { - responseList.addAll(request.get()); - if (status.isRollbackOnly()) { - return createErrorResponse(responseList, -1); - } - return responseList; - } catch (BatchExecutionException ex) { - status.setRollbackOnly(); - BatchResponse errResponse = new BatchResponse(); - errResponse.setStatusCode(ex.getErrorInfo().getStatusCode()); - errResponse.setRequestId(ex.getRequest().getRequestId()); - errResponse.setBody(ex.getErrorInfo().getMessage()); - return Arrays.asList(errResponse); - } catch (RuntimeException ex) { - status.setRollbackOnly(); - ErrorInfo e = errorHandler.handle(ex); - BatchResponse errResponse = new BatchResponse(); - errResponse.setStatusCode(e.getStatusCode()); - errResponse.setBody(e.getMessage()); - return Arrays.asList(errResponse); + Long requestId = null; + Integer statusCode = null; + String body = null; + Set
headers = new HashSet<>(); + if (ex != null) { + ErrorInfo errorInfo = errorHandler.handle(errorHandler.getMappable(ex)); + statusCode = errorInfo.getStatusCode(); + body = errorInfo.getMessage(); + headers = Optional.ofNullable(errorInfo.getHeaders()).orElse(new HashSet<>()); + } + if (response != null) { + requestId = response.getRequestId(); + if (response.getStatusCode() == null || response.getStatusCode() != SC_OK) { + if (response.getStatusCode() != null) { + statusCode = response.getStatusCode(); } - }); - } catch (TransactionException | NonTransientDataAccessException ex) { - ErrorInfo e = errorHandler.handle(ex); - return createErrorResponse(responseList, e.getStatusCode()); + body = "Transaction is being rolled back. First erroneous request: \n" + new Gson().toJson(response); + } + if (response.getHeaders() != null) { + headers.addAll(response.getHeaders()); + } } + return List.of(buildErrorResponse(requestId, statusCode, body, headers)); } + private BatchResponse buildErrorResponse(Long requestId, Integer statusCode, String body, Set
headers) { + return new BatchResponse().setRequestId(requestId).setStatusCode(statusCode == null ? SC_INTERNAL_SERVER_ERROR : statusCode) + .setBody(body == null ? "Request with id " + requestId + " was erroneous!" : body).setHeaders(headers); + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java index d0877a14b06..eb43781663f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java @@ -20,17 +20,14 @@ import lombok.Getter; import org.apache.fineract.batch.domain.BatchRequest; -import org.apache.fineract.batch.exception.ErrorInfo; @Getter public class BatchExecutionException extends RuntimeException { private final BatchRequest request; - private final ErrorInfo errorInfo; - public BatchExecutionException(BatchRequest request, RuntimeException ex, ErrorInfo errorInfo) { + public BatchExecutionException(BatchRequest request, RuntimeException ex) { super("Error executing batch request: " + request, ex); this.request = request; - this.errorInfo = errorInfo; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/service/ResolutionHelper.java b/fineract-core/src/main/java/org/apache/fineract/batch/service/ResolutionHelper.java index 98158890a72..f27e0afbfa3 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/service/ResolutionHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/service/ResolutionHelper.java @@ -30,6 +30,7 @@ import lombok.RequiredArgsConstructor; import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.batch.exception.BatchReferenceInvalidException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.springframework.stereotype.Component; @@ -55,23 +56,19 @@ public static class BatchRequestNode { private BatchRequest request; private final List childRequests = new ArrayList<>(); - public BatchRequestNode() { - + public BatchRequestNode(BatchRequest request) { + this.request = request; } public BatchRequest getRequest() { return this.request; } - public void setRequest(BatchRequest request) { - this.request = request; - } - - public List getChildRequests() { + public List getChildNodes() { return this.childRequests; } - public void addChildRequest(final BatchRequestNode batchRequest) { + public void addChildNode(final BatchRequestNode batchRequest) { this.childRequests.add(batchRequest); } @@ -84,35 +81,37 @@ public void addChildRequest(final BatchRequestNode batchRequest) { * different list is identified with a "Key" which is the "requestId" of the request at topmost level in dependency * hierarchy of that particular list. * - * @param batchRequests + * @param requests * @return List<ArrayList<BatchRequestNode>> */ - public List getDependingRequests(final List batchRequests) { - final List rootRequests = new ArrayList<>(); - - for (BatchRequest batchRequest : batchRequests) { - if (batchRequest.getReference() == null) { - final BatchRequestNode node = new BatchRequestNode(); - node.setRequest(batchRequest); - rootRequests.add(node); + public List buildNodesTree(final List requests) { + final List rootNodes = new ArrayList<>(); + for (BatchRequest request : requests) { + if (request.getReference() == null) { + final BatchRequestNode node = new BatchRequestNode(request); + rootNodes.add(node); } else { - this.addDependingRequest(batchRequest, rootRequests); + if (!addDependingRequest(request, rootNodes)) { + throw new BatchReferenceInvalidException(request.getReference()); + } } } - - return rootRequests; + return rootNodes; } - private void addDependingRequest(final BatchRequest batchRequest, final List parentRequests) { - for (BatchRequestNode batchRequestNode : parentRequests) { - if (batchRequestNode.getRequest().getRequestId().equals(batchRequest.getReference())) { - final BatchRequestNode dependingRequest = new BatchRequestNode(); - dependingRequest.setRequest(batchRequest); - batchRequestNode.addChildRequest(dependingRequest); + private boolean addDependingRequest(final BatchRequest request, final List parentNodes) { + for (BatchRequestNode parentNode : parentNodes) { + if (parentNode.getRequest().getRequestId().equals(request.getReference())) { + final BatchRequestNode childNode = new BatchRequestNode(request); + parentNode.addChildNode(childNode); + return true; } else { - addDependingRequest(batchRequest, batchRequestNode.getChildRequests()); + if (addDependingRequest(request, parentNode.getChildNodes())) { + return true; + } } } + return false; } /** @@ -123,34 +122,27 @@ private void addDependingRequest(final BatchRequest batchRequest, final List element : jsonRequestBody.entrySet()) { - final String key = element.getKey(); - final JsonElement value = resolveDependentVariables(element, responseCtx); - jsonResultBody.add(key, value); + String requestBody = request.getBody(); + if (requestBody != null) { + final JsonObject jsonRequestBody = this.fromJsonHelper.parse(requestBody).getAsJsonObject(); + JsonObject jsonResultBody = new JsonObject(); + // Iterate through each element in the requestBody to find dependent + // parameter + for (Map.Entry element : jsonRequestBody.entrySet()) { + final String key = element.getKey(); + final JsonElement value = resolveDependentVariables(element, responseCtx); + jsonResultBody.add(key, value); + } + // Set the body after dependency resolution + request.setBody(jsonResultBody.toString()); } - // Set the body after dependency resolution - br.setBody(jsonResultBody.toString()); - // Also check the relativeUrl for any dependency resolution String relativeUrl = request.getRelativeUrl(); - if (relativeUrl.contains("$.")) { - String queryParams = ""; if (relativeUrl.contains("?")) { queryParams = relativeUrl.substring(relativeUrl.indexOf("?")); @@ -158,24 +150,21 @@ public BatchRequest resoluteRequest(final BatchRequest request, final BatchRespo } final Iterable parameters = Splitter.on('/').split(relativeUrl); - for (String parameter : parameters) { if (parameter.contains("$.")) { final String resParamValue = responseCtx.read(parameter).toString(); relativeUrl = relativeUrl.replace(parameter, resParamValue); - br.setRelativeUrl(relativeUrl + queryParams); + request.setRelativeUrl(relativeUrl + queryParams); } } } - return br; + return request; } private JsonElement resolveDependentVariables(final Map.Entry entryElement, final ReadContext responseCtx) { - JsonElement value = null; - + JsonElement value; final JsonElement element = entryElement.getValue(); - if (element.isJsonObject()) { final JsonObject jsObject = element.getAsJsonObject(); value = processJsonObject(jsObject, responseCtx); @@ -202,16 +191,13 @@ private JsonElement processJsonObject(final JsonObject jsObject, final ReadConte } private JsonArray processJsonArray(final JsonArray elementArray, final ReadContext responseCtx) { - JsonArray valueArr = new JsonArray(); - for (JsonElement element : elementArray) { if (element.isJsonObject()) { final JsonObject jsObject = element.getAsJsonObject(); valueArr.add(processJsonObject(jsObject, responseCtx)); } } - return valueArr; } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandProcessingResultType.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandProcessingResultType.java index 770f579d042..299c1a0e2c8 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandProcessingResultType.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandProcessingResultType.java @@ -18,6 +18,9 @@ */ package org.apache.fineract.commands.domain; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -32,6 +35,14 @@ public enum CommandProcessingResultType { UNDER_PROCESSING(4, "commandProcessingResultType.underProcessing"), // ERROR(5, "commandProcessingResultType.error"); + private static final Map BY_ID = Arrays.stream(values()) + .collect(Collectors.toMap(CommandProcessingResultType::getValue, v -> v)); + private final Integer value; private final String code; + + public static CommandProcessingResultType fromInt(final Integer value) { + CommandProcessingResultType transactionType = BY_ID.get(value); + return transactionType == null ? INVALID : transactionType; + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java index e9c0d9f891d..c56a7c02bf7 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java @@ -123,16 +123,6 @@ public class CommandSource extends AbstractPersistableCustom { @Column(name = "result_status_code") private Integer resultStatusCode; - public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker, - String idempotencyKey, Integer status) { - return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(), - command.json(), maker, idempotencyKey, status); - } - - protected CommandSource() { - // - } - private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId, final Long subResourceId, final String commandSerializedAsJson, final AppUser maker, final String idempotencyKey, final Integer status) { @@ -143,11 +133,31 @@ private CommandSource(final String actionName, final String entityName, final St this.subResourceId = subResourceId; this.commandAsJson = commandSerializedAsJson; this.maker = maker; - this.madeOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); + this.madeOnDate = DateUtils.getAuditOffsetDateTime(); this.status = status; this.idempotencyKey = idempotencyKey; } + public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker, + String idempotencyKey, Integer status) { + CommandSource commandSource = new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), + command.subentityId(), command.json(), maker, idempotencyKey, status); + commandSource.officeId = wrapper.getOfficeId(); + commandSource.groupId = command.getGroupId(); + commandSource.clientId = command.getClientId(); + commandSource.loanId = command.getLoanId(); + commandSource.savingsId = command.getSavingsId(); + commandSource.productId = command.getProductId(); + commandSource.transactionId = command.getTransactionId(); + commandSource.creditBureauId = command.getCreditBureauId(); + commandSource.organisationCreditBureauId = command.getOrganisationCreditBureauId(); + return commandSource; + } + + protected CommandSource() { + // + } + public Long getCreditBureauId() { return this.creditBureauId; } @@ -160,31 +170,27 @@ public Long getOrganisationCreditBureauId() { return this.organisationCreditBureauId; } - public String getJobName() { - return this.jobName; - } - public void setOrganisationCreditBureauId(Long organisationCreditBureauId) { this.organisationCreditBureauId = organisationCreditBureauId; } - public void markAsChecked(final AppUser checker) { - this.checker = checker; - this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); - this.status = CommandProcessingResultType.PROCESSED.getValue(); + public String getJobName() { + return this.jobName; } - public void markAsRejected(final AppUser checker) { - this.checker = checker; - this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); - this.status = CommandProcessingResultType.REJECTED.getValue(); + public Long getResourceId() { + return this.resourceId; } - public void updateResourceId(final Long resourceId) { + public void setResourceId(final Long resourceId) { this.resourceId = resourceId; } - public void updateSubResourceId(final Long subResourceId) { + public Long getSubResourceId() { + return this.subResourceId; + } + + public void setSubResourceId(final Long subResourceId) { this.subResourceId = subResourceId; } @@ -196,14 +202,6 @@ public void setCommandJson(final String json) { this.commandAsJson = json; } - public Long resourceId() { - return this.resourceId; - } - - public Long subResourceId() { - return this.subResourceId; - } - public String getActionName() { return this.actionName; } @@ -216,36 +214,6 @@ public String getPermissionCode() { return this.actionName + "_" + this.entityName; } - public Long getResourceId() { - return this.resourceId; - } - - public Long getSubResourceId() { - return this.subResourceId; - } - - public void markAsAwaitingApproval() { - this.status = CommandProcessingResultType.AWAITING_APPROVAL.getValue(); - } - - public boolean isMarkedAsAwaitingApproval() { - return this.status.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue()); - } - - public void updateForAudit(final CommandProcessingResult result) { - this.officeId = result.getOfficeId(); - this.groupId = result.getGroupId(); - this.clientId = result.getClientId(); - this.loanId = result.getLoanId(); - this.savingsId = result.getSavingsId(); - this.productId = result.getProductId(); - this.transactionId = result.getTransactionId(); - this.resourceId = result.getResourceId(); - this.resourceExternalId = result.getResourceExternalId(); - this.subResourceId = result.getSubResourceId(); - this.subResourceExternalId = result.getSubResourceExternalId(); - } - public String getResourceGetUrl() { return this.resourceGetUrl; } @@ -296,7 +264,7 @@ public String getTransactionId() { return this.transactionId; } - public void updateTransaction(final String transactionId) { + public void setTransactionId(final String transactionId) { this.transactionId = transactionId; } @@ -324,6 +292,10 @@ public void setStatus(Integer status) { this.status = status; } + public void setStatus(CommandProcessingResultType status) { + setStatus(status == null ? null : status.getValue()); + } + public Integer getResultStatusCode() { return resultStatusCode; } @@ -331,4 +303,38 @@ public Integer getResultStatusCode() { public void setResultStatusCode(Integer resultStatusCode) { this.resultStatusCode = resultStatusCode; } + + public void markAsAwaitingApproval() { + this.status = CommandProcessingResultType.AWAITING_APPROVAL.getValue(); + } + + public boolean isMarkedAsAwaitingApproval() { + return this.status.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue()); + } + + public void markAsChecked(final AppUser checker) { + this.checker = checker; + this.checkedOnDate = DateUtils.getAuditOffsetDateTime(); + this.status = CommandProcessingResultType.PROCESSED.getValue(); + } + + public void markAsRejected(final AppUser checker) { + this.checker = checker; + this.checkedOnDate = DateUtils.getAuditOffsetDateTime(); + this.status = CommandProcessingResultType.REJECTED.getValue(); + } + + public void updateForAudit(final CommandProcessingResult result) { + this.officeId = result.getOfficeId(); + this.groupId = result.getGroupId(); + this.clientId = result.getClientId(); + this.loanId = result.getLoanId(); + this.savingsId = result.getSavingsId(); + this.productId = result.getProductId(); + this.transactionId = result.getTransactionId(); + this.resourceId = result.getResourceId(); + this.resourceExternalId = result.getResourceExternalId(); + this.subResourceId = result.getSubResourceId(); + this.subResourceExternalId = result.getSubResourceExternalId(); + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java index c28ac346638..99a340a8e9d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java @@ -302,6 +302,10 @@ public String getTaskPermissionName() { return this.taskPermissionName; } + public Long getOfficeId() { + return officeId; + } + public Long getGroupId() { return this.groupId; } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/jobs/PurgeProcessedCommandsTasklet.java b/fineract-core/src/main/java/org/apache/fineract/commands/jobs/PurgeProcessedCommandsTasklet.java index ba81cdfbb8a..de7dc4bf2e4 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/jobs/PurgeProcessedCommandsTasklet.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/jobs/PurgeProcessedCommandsTasklet.java @@ -43,7 +43,7 @@ public class PurgeProcessedCommandsTasklet implements Tasklet { public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { try { Long numberOfDaysForPurgeCriteria = configurationDomainService.retrieveProcessedCommandsPurgeDaysCriteria(); - OffsetDateTime dateForPurgeCriteria = DateUtils.getOffsetDateTimeOfTenant().minusDays(numberOfDaysForPurgeCriteria); + OffsetDateTime dateForPurgeCriteria = DateUtils.getAuditOffsetDateTime().minusDays(numberOfDaysForPurgeCriteria); repository.deleteOlderEventsWithStatus(CommandProcessingResultType.PROCESSED.getValue(), dateForPurgeCriteria); } catch (Exception e) { log.error("Error occurred while purging processed commands: ", e); diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java index f58c7ef8d23..3ac4e3382f0 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java @@ -18,7 +18,6 @@ */ package org.apache.fineract.commands.service; -import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR; import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING; import lombok.RequiredArgsConstructor; @@ -48,46 +47,48 @@ public class CommandSourceService { private final CommandSourceRepository commandSourceRepository; private final ErrorHandler errorHandler; + @NotNull @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ) - public CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) { - return saveInitialInternal(wrapper, jsonCommand, maker, idempotencyKey); + public CommandSource saveInitialNewTransaction(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) { + return saveInitial(wrapper, jsonCommand, maker, idempotencyKey); } - public CommandSource saveInitialNoTransaction(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) { - return saveInitialInternal(wrapper, jsonCommand, maker, idempotencyKey); + @NotNull + @Transactional(propagation = Propagation.REQUIRED) + public CommandSource saveInitialSameTransaction(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) { + return saveInitial(wrapper, jsonCommand, maker, idempotencyKey); } @NotNull - private CommandSource saveInitialInternal(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) { + private CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) { CommandSource initialCommandSource = getInitialCommandSource(wrapper, jsonCommand, maker, idempotencyKey); - if (initialCommandSource.getCommandJson() == null) { initialCommandSource.setCommandJson("{}"); } - return commandSourceRepository.saveAndFlush(initialCommandSource); } - public void saveFailed(CommandSource commandSource) { - commandSource.setStatus(ERROR.getValue()); - commandSourceRepository.saveAndFlush(commandSource); + @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ) + public CommandSource saveResultNewTransaction(@NotNull CommandSource commandSource) { + return saveResult(commandSource); } - @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ) - public CommandSource saveResult(CommandSource commandSource) { - return commandSourceRepository.saveAndFlush(commandSource); + @Transactional(propagation = Propagation.REQUIRED) + public CommandSource saveResultSameTransaction(@NotNull CommandSource commandSource) { + return saveResult(commandSource); } - public CommandSource saveResultNoTransaction(CommandSource commandSource) { + @NotNull + private CommandSource saveResult(@NotNull CommandSource commandSource) { return commandSourceRepository.saveAndFlush(commandSource); } - public ErrorInfo generateErrorException(Throwable t) { - if (t instanceof final RuntimeException e) { - return errorHandler.handle(e); - } else { - return new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString() + "}"); - } + public ErrorInfo generateErrorInfo(Throwable t) { + return errorHandler.handle(errorHandler.getMappable(t)); + } + + public CommandSource getCommandSource(Long commandSourceId) { + return commandSourceRepository.findById(commandSourceId).orElseThrow(() -> new CommandNotFoundException(commandSourceId)); } public CommandSource findCommandSource(CommandWrapper wrapper, String idempotencyKey) { diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index a0d1431bf06..b2d9e938ebd 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -737,7 +737,6 @@ public CommandWrapperBuilder deleteDatatableEntry(final String datatable, final } private void commonDatatableSettings(final String datatable, final Long apptableId, final Long datatableId) { - this.entityName = datatable; this.entityId = apptableId; this.subentityId = datatableId; diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index 42cc37b4232..225c25f88a1 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -82,15 +82,15 @@ public CommandProcessingResult approveEntry(final Long makerCheckerId) { validateIsUpdateAllowed(); final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(makerCheckerId, commandSourceInput.getActionName(), - commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subResourceId(), + commandSourceInput.getEntityName(), commandSourceInput.getResourceId(), commandSourceInput.getSubResourceId(), commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getOfficeId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(), commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getIdempotencyKey()); final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.getCommandJson()); final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.getCommandJson(), parsedCommand, - this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(), - commandSourceInput.subResourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(), + this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.getResourceId(), + commandSourceInput.getSubResourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getCreditBureauId(), commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName()); diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java index 233a2639d56..e45814a9102 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java @@ -21,6 +21,7 @@ import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR; import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED; import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING; +import static org.apache.http.HttpStatus.SC_OK; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -29,9 +30,9 @@ import java.time.Instant; import java.util.HashMap; import java.util.Map; -import java.util.function.Function; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.batch.exception.ErrorHandler; import org.apache.fineract.batch.exception.ErrorInfo; import org.apache.fineract.commands.domain.CommandProcessingResultType; import org.apache.fineract.commands.domain.CommandSource; @@ -46,7 +47,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; -import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException; import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException; import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException; import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException; @@ -79,6 +79,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer private final IdempotencyKeyResolver idempotencyKeyResolver; private final IdempotencyKeyGenerator idempotencyKeyGenerator; private final CommandSourceService commandSourceService; + private final ErrorHandler errorHandler; private final FineractRequestContextHolder fineractRequestContextHolder; private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); @@ -90,99 +91,102 @@ public CommandProcessingResult executeCommand(final CommandWrapper wrapper, fina // Do not store the idempotency key because of the exception handling setIdempotencyKeyStoreFlag(false); - final boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName()); - String idempotencyKey = idempotencyKeyResolver.resolve(wrapper); - exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey); + Long commandId = (Long) fineractRequestContextHolder.getAttribute(COMMAND_SOURCE_ID, null); + boolean isRetry = commandId != null; - // Store idempotency key to the request attribute - CommandSource savedCommandSource; - if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) { - savedCommandSource = commandSourceService.saveInitialNoTransaction(wrapper, command, context.authenticatedUser(wrapper), - idempotencyKey); + CommandSource commandSource = null; + String idempotencyKey; + if (isRetry) { + commandSource = commandSourceService.getCommandSource(commandId); + idempotencyKey = commandSource.getIdempotencyKey(); } else { - savedCommandSource = commandSourceService.saveInitial(wrapper, command, context.authenticatedUser(wrapper), idempotencyKey); + idempotencyKey = idempotencyKeyResolver.resolve(wrapper); + } + exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey, isRetry); + + boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent(); + if (commandSource == null) { + AppUser user = context.authenticatedUser(wrapper); + commandSource = sameTransaction ? commandSourceService.saveInitialSameTransaction(wrapper, command, user, idempotencyKey) + : commandSourceService.saveInitialNewTransaction(wrapper, command, user, idempotencyKey); + storeCommandIdInContext(commandSource); // Store command id as a request attribute } - storeCommandToIdempotentFilter(savedCommandSource); setIdempotencyKeyStoreFlag(true); final CommandProcessingResult result; try { result = findCommandHandler(wrapper).processCommand(command); } catch (Throwable t) { // NOSONAR - CommandSource source = commandSourceService.findCommandSource(wrapper, idempotencyKey); - commandSourceService.saveFailed(source); - publishHookErrorEvent(wrapper, command, t); + ErrorInfo errorInfo = commandSourceService.generateErrorInfo(t); + commandSource.setResultStatusCode(errorInfo.getStatusCode()); + commandSource.setResult(errorInfo.getMessage()); + commandSource.setStatus(ERROR); + commandSource = sameTransaction ? commandSourceService.saveResultSameTransaction(commandSource) + : commandSourceService.saveResultNewTransaction(commandSource); + publishHookErrorEvent(wrapper, command, errorInfo); throw t; } - CommandSource initialCommandSource = commandSourceService.findCommandSource(wrapper, idempotencyKey); - initialCommandSource.setResult(toApiJsonSerializer.serializeResult(result)); - initialCommandSource.updateResourceId(result.getResourceId()); - initialCommandSource.updateForAudit(result); + commandSource.updateForAudit(result); + commandSource.setResult(toApiJsonSerializer.serializeResult(result)); + commandSource.setResultStatusCode(SC_OK); + commandSource.setStatus(PROCESSED); - boolean rollBack = (rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker; - if (result.hasChanges() && !rollBack) { - initialCommandSource.setCommandJson(toApiJsonSerializer.serializeResult(result.getChanges())); + boolean isRollback = !isApprovedByChecker && (result.isRollbackTransaction() + || configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName())); + // TODO: this should be removed, can not override audit information (and maker-checker does not work) + if (!isRollback && result.hasChanges()) { + commandSource.setCommandJson(toApiJsonSerializer.serializeResult(result.getChanges())); } - initialCommandSource.setStatus(CommandProcessingResultType.PROCESSED.getValue()); - if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) { - commandSourceService.saveResultNoTransaction(initialCommandSource); - } else { - commandSourceService.saveResult(initialCommandSource); - } + commandSource = commandSourceService.saveResultSameTransaction(commandSource); - if ((rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker) { + if (isRollback) { /* * JournalEntry will generate a new transactionId every time. Updating the transactionId with old * transactionId, because as there are no entries are created with new transactionId, will throw an error * when checker approves the transaction */ - initialCommandSource.updateTransaction(command.getTransactionId()); - /* - * Update CommandSource json data with JsonCommand json data, line 77 and 81 may update the json data - */ - initialCommandSource.setCommandJson(command.json()); - throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(initialCommandSource); + commandSource.setTransactionId(command.getTransactionId()); + // TODO: this should be removed together with lines 133-135 + commandSource.setCommandJson(command.json()); // Set back CommandSource json data + throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(commandSource); } - result.setRollbackTransaction(null); + result.setRollbackTransaction(null); publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, result); return result; } - private void storeCommandToIdempotentFilter(CommandSource savedCommandSource) { + private void storeCommandIdInContext(CommandSource savedCommandSource) { if (savedCommandSource.getId() == null) { throw new IllegalStateException("Command source not saved"); } - saveCommandToRequest(savedCommandSource); - } - - private void saveCommandToRequest(CommandSource savedCommandSource) { + // Idempotency filters and retry need this fineractRequestContextHolder.setAttribute(COMMAND_SOURCE_ID, savedCommandSource.getId()); } - private void publishHookErrorEvent(CommandWrapper wrapper, JsonCommand command, Throwable t) { - ErrorInfo ex = commandSourceService.generateErrorException(t); - publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, gson.toJson(ex)); + private void publishHookErrorEvent(CommandWrapper wrapper, JsonCommand command, ErrorInfo errorInfo) { + publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, gson.toJson(errorInfo)); } - private void exceptionWhenTheRequestAlreadyProcessed(CommandWrapper wrapper, String idempotencyKey) { - CommandSource existingCommand = commandSourceService.findCommandSource(wrapper, idempotencyKey); - if (existingCommand != null) { - idempotentExceptionByStatus(UNDER_PROCESSING, existingCommand, - command -> new IdempotentCommandProcessUnderProcessingException(wrapper)); - idempotentExceptionByStatus(ERROR, existingCommand, command -> new IdempotentCommandProcessFailedException(wrapper, command)); - idempotentExceptionByStatus(PROCESSED, existingCommand, - command -> new IdempotentCommandProcessSucceedException(wrapper, command.getResult(), command.getResultStatusCode())); + private void exceptionWhenTheRequestAlreadyProcessed(CommandWrapper wrapper, String idempotencyKey, boolean retry) { + CommandSource command = commandSourceService.findCommandSource(wrapper, idempotencyKey); + if (command == null) { + return; } - } - - private void idempotentExceptionByStatus(CommandProcessingResultType status, CommandSource command, - Function exceptionMapper) { - if (status.getValue().equals(command.getStatus())) { - throw exceptionMapper.apply(command); + CommandProcessingResultType status = CommandProcessingResultType.fromInt(command.getStatus()); + switch (status) { + case UNDER_PROCESSING -> throw new IdempotentCommandProcessUnderProcessingException(wrapper, idempotencyKey); + case PROCESSED -> throw new IdempotentCommandProcessSucceedException(wrapper, idempotencyKey, command); + case ERROR -> { + if (!retry) { + throw new IdempotentCommandProcessFailedException(wrapper, idempotencyKey, command); + } + } + default -> { + } } } @@ -192,24 +196,23 @@ private void setIdempotencyKeyStoreFlag(boolean flag) { @Transactional @Override - public CommandProcessingResult logCommand(CommandSource commandSourceResult) { - commandSourceResult.markAsAwaitingApproval(); - if (commandSourceResult.getIdempotencyKey() == null) { - commandSourceResult.setIdempotencyKey(idempotencyKeyGenerator.create()); + public CommandProcessingResult logCommand(CommandSource commandSource) { + commandSource.markAsAwaitingApproval(); + if (commandSource.getIdempotencyKey() == null) { + commandSource.setIdempotencyKey(idempotencyKeyGenerator.create()); } - commandSourceResult = commandSourceService.saveResultNoTransaction(commandSourceResult); + commandSource = commandSourceService.saveResultSameTransaction(commandSource); - return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId()) - .withEntityId(commandSourceResult.getResourceId()).build(); + return new CommandProcessingResultBuilder().withCommandId(commandSource.getId()).withEntityId(commandSource.getResourceId()) + .build(); } @SuppressWarnings("unused") - private CommandProcessingResult fallbackExecuteCommand(Exception e) throws Exception { + private CommandProcessingResult fallbackExecuteCommand(Exception e) { if (e instanceof RollbackTransactionAsCommandIsNotApprovedByCheckerException ex) { return logCommand(ex.getCommandSourceResult()); } - - throw e; + throw errorHandler.getMappable(e); } private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) { @@ -278,7 +281,6 @@ public boolean validateCommand(final CommandWrapper commandWrapper, final AppUse } private void publishHookEvent(final String entityName, final String actionName, JsonCommand command, final Object result) { - try { final AppUser appUser = context.authenticatedUser(CommandWrapper.wrap(actionName, entityName, null, null)); diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceImpl.java index 9b7d2f2c854..00a8711284d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceImpl.java @@ -19,7 +19,6 @@ package org.apache.fineract.infrastructure.businessdate.service; import java.time.LocalDate; -import java.time.ZoneId; import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -70,8 +69,7 @@ public BusinessDateData findByType(String type) { @Override public HashMap getBusinessDates() { HashMap businessDateMap = new HashMap<>(); - ZoneId zone = DateUtils.getDateTimeZoneOfTenant(); - LocalDate tenantDate = LocalDate.now(zone); + LocalDate tenantDate = DateUtils.getLocalDateOfTenant(); businessDateMap.put(BusinessDateType.BUSINESS_DATE, tenantDate); businessDateMap.put(BusinessDateType.COB_DATE, tenantDate); if (configurationDomainService.isBusinessDateEnabled()) { @@ -80,7 +78,6 @@ public HashMap getBusinessDates() { businessDateMap.put(BusinessDateType.valueOf(businessDateData.getType()), businessDateData.getDate()); } } - return businessDateMap; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceImpl.java index b1671ebb0fb..9b25f49375f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceImpl.java @@ -132,15 +132,11 @@ private void updateOrCreateBusinessDate(String type, LocalDate newDate, Map changes) { LocalDate oldDate = businessDate.getDate(); - if (!hasChange(oldDate, newDate)) { + if (DateUtils.isEqual(oldDate, newDate)) { return; } businessDate.setDate(newDate); repository.save(businessDate); changes.put(businessDate.getType().name(), newDate); } - - private boolean hasChange(@NotNull LocalDate oldDate, @NotNull LocalDate date) { - return (date.isBefore(oldDate) || date.isAfter(oldDate)); - } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java index 95a72ab1886..259f8dfd42c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java @@ -18,16 +18,27 @@ */ package org.apache.fineract.infrastructure.core.data; +import static lombok.AccessLevel.PROTECTED; +import static org.apache.http.HttpStatus.SC_BAD_REQUEST; +import static org.apache.http.HttpStatus.SC_CONFLICT; +import static org.apache.http.HttpStatus.SC_FORBIDDEN; +import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.apache.http.HttpStatus.SC_LOCKED; +import static org.apache.http.HttpStatus.SC_METHOD_NOT_ALLOWED; +import static org.apache.http.HttpStatus.SC_NOT_FOUND; +import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE; +import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; + import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.Gson; -import jakarta.ws.rs.core.Response.Status; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import lombok.Getter; +import lombok.Setter; -/** - * - */ +@Getter +@Setter(PROTECTED) public class ApiGlobalErrorResponse { /** @@ -58,176 +69,125 @@ public class ApiGlobalErrorResponse { private List errors = new ArrayList<>(); - public static ApiGlobalErrorResponse unAuthenticated() { - - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("401"); - globalErrorResponse.setDeveloperMessage("Invalid authentication details were passed in api request."); - globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authenticated"); - globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login."); + protected ApiGlobalErrorResponse() {} - return globalErrorResponse; + public static ApiGlobalErrorResponse create(int statusCode, String msgCode, String developerMessage, String defaultUserMessage, + List errors) { + ApiGlobalErrorResponse response = new ApiGlobalErrorResponse(); + response.setHttpStatusCode(String.valueOf(statusCode)); + response.setUserMessageGlobalisationCode(msgCode); + response.setDeveloperMessage(developerMessage); + response.setDefaultUserMessage(defaultUserMessage); + response.setErrors(errors); + return response; } - public static ApiGlobalErrorResponse invalidTenantIdentifier() { + public static ApiGlobalErrorResponse create(int statusCode, String msgCode, String developerMessage, String defaultUserMessage) { + return create(statusCode, msgCode, developerMessage, defaultUserMessage, null); + } - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("401"); - globalErrorResponse.setDeveloperMessage("Invalid tenant details were passed in api request."); - globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.tenant.identifier"); - globalErrorResponse.setDefaultUserMessage("Invalid tenant identifier provided with request."); + public static ApiGlobalErrorResponse unAuthenticated() { + return create(SC_UNAUTHORIZED, "error.msg.not.authenticated", "Invalid authentication details were passed in api request.", + "Unauthenticated. Please login."); + } - return globalErrorResponse; + public static ApiGlobalErrorResponse invalidTenantIdentifier() { + return create(SC_UNAUTHORIZED, "error.msg.invalid.tenant.identifier", "Invalid tenant details were passed in api request.", + "Invalid tenant identifier provided with request."); } public static ApiGlobalErrorResponse invalidInstanceTypeMethod(final String method) { - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode(Status.METHOD_NOT_ALLOWED.toString()); - globalErrorResponse.setDeveloperMessage("Invalid instance type called in api request for the method " + method); - globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.instance.type"); - globalErrorResponse.setDefaultUserMessage("Invalid method " + method + " used with request to this instance type."); - - return globalErrorResponse; + return create(SC_METHOD_NOT_ALLOWED, "error.msg.invalid.instance.type", + "Invalid instance type called in api request for the method " + method, + "Invalid method " + method + " used with request to this instance type."); } public static ApiGlobalErrorResponse loanIsLocked(final Long loanId) { - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode(Status.CONFLICT.toString()); - globalErrorResponse.setDeveloperMessage("Loan is locked by the COB job. Loan ID: " + loanId); - globalErrorResponse.setUserMessageGlobalisationCode("error.msg.loan.locked"); - globalErrorResponse.setDefaultUserMessage("Loan is locked by the COB job. Loan ID: \" + loanId"); + String msg = "Loan is locked by the COB job. Loan ID: " + loanId; + return create(SC_CONFLICT, "error.msg.loan.locked", msg, msg); + } - return globalErrorResponse; + public static ApiGlobalErrorResponse locked(String type, String identifier) { + String details = ""; + if (type == null) { + type = "unknown"; + } else { + details = " on " + type; + } + if (identifier != null) { + details += " [" + identifier + ']'; + } + String msg = "The server is currently unable to handle the request due to concurrent modification" + details + ", please try again"; + return create(SC_LOCKED, "error.msg.platform.service." + type + ".conflict", msg, msg); } public static ApiGlobalErrorResponse unAuthorized(final String defaultUserMessage) { - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("403"); - globalErrorResponse.setDeveloperMessage( - "The user associated with credentials passed on this request does not have sufficient privileges to perform this action."); - globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authorized"); - globalErrorResponse.setDefaultUserMessage("Insufficient privileges to perform this action."); + String msgCode = "error.msg.not.authorized"; + final List errors = List.of(ApiParameterError.generalError(msgCode, defaultUserMessage)); - final List errors = new ArrayList<>(); - errors.add(ApiParameterError.generalError("error.msg.not.authorized", defaultUserMessage)); - globalErrorResponse.setErrors(errors); - - return globalErrorResponse; + return create(SC_FORBIDDEN, msgCode, + "The user associated with credentials passed on this request does not have sufficient privileges to perform this action.", + "Insufficient privileges to perform this action.", errors); } public static ApiGlobalErrorResponse domainRuleViolation(final String globalisationMessageCode, final String defaultUserMessage, final Object... defaultUserMessageArgs) { - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("403"); - globalErrorResponse.setDeveloperMessage("Request was understood but caused a domain rule violation."); - globalErrorResponse.setUserMessageGlobalisationCode("validation.msg.domain.rule.violation"); - globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation."); - final List errors = new ArrayList<>(); errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); - globalErrorResponse.setErrors(errors); - return globalErrorResponse; - } - - public static ApiGlobalErrorResponse notFound(final String globalisationMessageCode, final String defaultUserMessage, - final Object... defaultUserMessageArgs) { - - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("404"); - globalErrorResponse.setDeveloperMessage("The requested resource is not available."); - globalErrorResponse.setUserMessageGlobalisationCode("error.msg.resource.not.found"); - globalErrorResponse.setDefaultUserMessage("The requested resource is not available."); - - final List errors = new ArrayList<>(); - errors.add(ApiParameterError.resourceIdentifierNotFound(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); - globalErrorResponse.setErrors(errors); - - return globalErrorResponse; + return create(SC_FORBIDDEN, "validation.msg.domain.rule.violation", "Request was understood but caused a domain rule violation.", + "Errors contain reason for domain rule violation.", errors); } public static ApiGlobalErrorResponse dataIntegrityError(final String globalisationMessageCode, final String defaultUserMessage, final String parameterName, final Object... defaultUserMessageArgs) { - - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("403"); - globalErrorResponse.setDeveloperMessage("The request caused a data integrity issue to be fired by the database."); - globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode); - globalErrorResponse.setDefaultUserMessage(defaultUserMessage); - final List errors = new ArrayList<>(); errors.add(ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName, defaultUserMessageArgs)); - globalErrorResponse.setErrors(errors); - return globalErrorResponse; + return create(SC_FORBIDDEN, globalisationMessageCode, "The request caused a data integrity issue to be fired by the database.", + defaultUserMessage, errors); } - public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage, - final List errors) { - - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("400"); - globalErrorResponse - .setDeveloperMessage("The request was invalid. This typically will happen due to validation errors which are provided."); - globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode); - globalErrorResponse.setDefaultUserMessage(defaultUserMessage); - - globalErrorResponse.setErrors(errors); + public static ApiGlobalErrorResponse notFound(final String globalisationMessageCode, final String defaultUserMessage, + final Object... defaultUserMessageArgs) { + String msg = "The requested resource is not available."; + final List errors = new ArrayList<>(); + errors.add(ApiParameterError.resourceIdentifierNotFound(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); - return globalErrorResponse; + return create(SC_NOT_FOUND, "error.msg.resource.not.found", msg, msg, errors); } - public static ApiGlobalErrorResponse jobIsDisabled(final String globalisationMessageCode, final String defaultUserMessage) { - - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode(Status.FORBIDDEN.toString()); - globalErrorResponse.setDeveloperMessage(defaultUserMessage); - globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode); - globalErrorResponse.setDefaultUserMessage(defaultUserMessage); - - return globalErrorResponse; + public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage, + final List errors) { + return create(SC_BAD_REQUEST, globalisationMessageCode, + "The request was invalid. This typically will happen due to validation errors which are provided.", defaultUserMessage, + errors); } public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage) { return badClientRequest(globalisationMessageCode, defaultUserMessage, Collections.emptyList()); } + public static ApiGlobalErrorResponse jobIsDisabled(final String globalisationMessageCode, final String defaultUserMessage) { + return create(SC_FORBIDDEN, globalisationMessageCode, defaultUserMessage, defaultUserMessage); + } + public static ApiGlobalErrorResponse serverSideError(final String globalisationMessageCode, final String defaultUserMessage, final Object... defaultUserMessageArgs) { - - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("500"); - globalErrorResponse.setDeveloperMessage("An unexpected error occured on the platform server."); - globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.server.side.error"); - globalErrorResponse.setDefaultUserMessage("An unexpected error occured on the platform server."); - + String msg = "An unexpected error occured on the platform server."; final List errors = new ArrayList<>(); errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); - globalErrorResponse.setErrors(errors); - return globalErrorResponse; + return create(SC_INTERNAL_SERVER_ERROR, "error.msg.platform.server.side.error", msg, msg, errors); } public static ApiGlobalErrorResponse serviceUnavailable(final String globalisationMessageCode, final String defaultUserMessage, final Object... defaultUserMessageArgs) { - - final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - globalErrorResponse.setHttpStatusCode("503"); - globalErrorResponse.setDeveloperMessage("The server is currently unable to handle the request , please try after some time."); - globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.service.unavailable"); - globalErrorResponse.setDefaultUserMessage("The server is currently unable to handle the request , please try after some time."); - + String msg = "The server is currently unable to handle the request , please try after some time."; final List errors = new ArrayList<>(); errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); - globalErrorResponse.setErrors(errors); - return globalErrorResponse; - } - - protected ApiGlobalErrorResponse() {} - - public ApiGlobalErrorResponse(final List errors) { - this.errors = errors; + return create(SC_SERVICE_UNAVAILABLE, "error.msg.platform.service.unavailable", msg, msg, errors); } @JsonProperty("errors") @@ -235,42 +195,6 @@ public List getErrors() { return this.errors; } - public void setErrors(final List errors) { - this.errors = errors; - } - - public String getDeveloperMessage() { - return this.developerMessage; - } - - public void setDeveloperMessage(final String developerMessage) { - this.developerMessage = developerMessage; - } - - public String getHttpStatusCode() { - return this.httpStatusCode; - } - - public void setHttpStatusCode(final String httpStatusCode) { - this.httpStatusCode = httpStatusCode; - } - - public String getDefaultUserMessage() { - return this.defaultUserMessage; - } - - public void setDefaultUserMessage(final String defaultUserMessage) { - this.defaultUserMessage = defaultUserMessage; - } - - public String getUserMessageGlobalisationCode() { - return this.userMessageGlobalisationCode; - } - - public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) { - this.userMessageGlobalisationCode = userMessageGlobalisationCode; - } - public String toJson() { final Gson gson = new Gson(); return gson.toJson(this); diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java index 70123b19b07..8be8de3888a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java @@ -19,7 +19,6 @@ package org.apache.fineract.infrastructure.core.data; import java.io.Serializable; -import java.util.HashMap; import java.util.Map; import lombok.Getter; import lombok.ToString; @@ -51,12 +50,43 @@ public class CommandProcessingResult implements Serializable { private final ExternalId resourceExternalId; private final ExternalId subResourceExternalId; - public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult) { - return new CommandProcessingResult(commandResult.commandId, commandResult.officeId, commandResult.groupId, commandResult.clientId, - commandResult.loanId, commandResult.savingsId, commandResult.resourceIdentifier, commandResult.resourceId, - commandResult.transactionId, commandResult.changes, commandResult.productId, commandResult.gsimId, commandResult.glimId, - commandResult.creditBureauReportData, commandResult.rollbackTransaction, commandResult.subResourceId, - commandResult.resourceExternalId, commandResult.subResourceExternalId); + private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, + final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId, + final Map changes, final Long productId, final Long gsimId, final Long glimId, + final Map creditBureauReportData, Boolean rollbackTransaction, final Long subResourceId, + final ExternalId resourceExternalId, final ExternalId subResourceExternalId) { + this.commandId = commandId; + this.officeId = officeId; + this.groupId = groupId; + this.clientId = clientId; + this.loanId = loanId; + this.savingsId = savingsId; + this.resourceIdentifier = resourceIdentifier; + this.resourceId = resourceId; + this.changes = changes; + this.transactionId = transactionId; + this.productId = productId; + this.gsimId = gsimId; + this.glimId = glimId; + this.creditBureauReportData = creditBureauReportData; + this.rollbackTransaction = rollbackTransaction; + this.subResourceId = subResourceId; + this.resourceExternalId = resourceExternalId; + this.subResourceExternalId = subResourceExternalId; + } + + protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map changes, + Long clientId) { + this(commandId, officeId, null, clientId, null, null, resourceId == null ? null : resourceId.toString(), resourceId, null, changes, + null, null, null, null, null, null, ExternalId.empty(), ExternalId.empty()); + } + + protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map changes) { + this(resourceId, officeId, commandId, changes, null); + } + + protected CommandProcessingResult(final Long resourceId) { + this(resourceId, null, null, null); } public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult, final Long resourceId) { @@ -67,6 +97,10 @@ public static CommandProcessingResult fromCommandProcessingResult(CommandProcess commandResult.resourceExternalId, commandResult.subResourceExternalId); } + public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult) { + return fromCommandProcessingResult(commandResult, commandResult.getResourceId()); + } + public static CommandProcessingResult fromDetails(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String resourceIdentifier, final Long entityId, final Long gsimId, final Long glimId, final Map creditBureauReportData, final String transactionId, @@ -81,21 +115,16 @@ public static CommandProcessingResult commandOnlyResult(final Long commandId) { return new CommandProcessingResult(null, null, commandId, null); } - public static CommandProcessingResult resourceResult(final Long resourceId, final Long commandId) { - return new CommandProcessingResult(resourceId, null, commandId, null); - } - public static CommandProcessingResult resourceResult(final Long resourceId, final Long commandId, final Map changes) { return new CommandProcessingResult(resourceId, null, commandId, changes); } - public static CommandProcessingResult subResourceResult(final Long resourceId, final Long subResourceId, final Long commandId) { - return new CommandProcessingResult(resourceId, subResourceId, commandId, null); + public static CommandProcessingResult resourceResult(final Long resourceId, final Long commandId) { + return new CommandProcessingResult(resourceId, null, commandId, null); } - public static CommandProcessingResult subResourceResult(final Long resourceId, final Long subResourceId, final Long commandId, - final Map changes) { - return new CommandProcessingResult(resourceId, subResourceId, commandId, changes); + public static CommandProcessingResult resourceResult(final Long resourceId) { + return new CommandProcessingResult(resourceId); } public static CommandProcessingResult withChanges(final Long resourceId, final Map changes) { @@ -106,88 +135,6 @@ public static CommandProcessingResult empty() { return new CommandProcessingResult(null, null, null, null); } - /* - * Deprecated - */ - public CommandProcessingResult(final Long entityId) { - if (entityId != null) { - this.resourceIdentifier = entityId.toString(); - } else { - this.resourceIdentifier = null; - } - this.resourceId = entityId; - this.officeId = null; - this.groupId = null; - this.clientId = null; - this.loanId = null; - this.savingsId = null; - this.transactionId = null; - this.changes = new HashMap<>(); - this.productId = null; - this.gsimId = null; - this.glimId = null; - this.creditBureauReportData = null; - this.subResourceId = null; - this.resourceExternalId = ExternalId.empty(); - this.subResourceExternalId = ExternalId.empty(); - } - - private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, - final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId, - final Map changesOnly, final Long productId, final Long gsimId, final Long glimId, - final Map creditBureauReportData, Boolean rollbackTransaction, final Long subResourceId, - final ExternalId resourceExternalId, final ExternalId subResourceExternalId) { - this.commandId = commandId; - this.officeId = officeId; - this.groupId = groupId; - this.clientId = clientId; - this.loanId = loanId; - this.savingsId = savingsId; - this.resourceIdentifier = resourceIdentifier; - this.resourceId = resourceId; - this.changes = changesOnly; - this.transactionId = transactionId; - this.productId = productId; - this.gsimId = gsimId; - this.glimId = glimId; - this.creditBureauReportData = creditBureauReportData; - this.rollbackTransaction = rollbackTransaction; - this.subResourceId = subResourceId; - this.resourceExternalId = resourceExternalId; - this.subResourceExternalId = subResourceExternalId; - } - - protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, - final Map changesOnly) { - if (resourceId != null) { - this.resourceIdentifier = resourceId.toString(); - } else { - this.resourceIdentifier = null; - } - this.resourceId = resourceId; - this.officeId = officeId; - this.groupId = null; - this.clientId = null; - this.loanId = null; - this.savingsId = null; - this.transactionId = null; - this.commandId = commandId; - this.changes = changesOnly; - this.productId = null; - this.gsimId = null; - this.glimId = null; - this.creditBureauReportData = null; - this.subResourceId = null; - this.resourceExternalId = ExternalId.empty(); - this.subResourceExternalId = ExternalId.empty(); - } - - protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, - final Map changesOnly, long clientId) { - this(commandId, officeId, null, clientId, null, null, null, resourceId, null, changesOnly, null, null, null, null, null, null, - ExternalId.empty(), ExternalId.empty()); - } - public void setOfficeId(final Long officeId) { this.officeId = officeId; } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java index 9b8cf9f7e9f..1536f08be42 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java @@ -128,7 +128,7 @@ public CommandProcessingResultBuilder withCreditReport(final Map } public CommandProcessingResultBuilder setRollbackTransaction(final boolean rollbackTransaction) { - this.rollbackTransaction = this.rollbackTransaction || rollbackTransaction; + this.rollbackTransaction |= rollbackTransaction; return this; } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java index 29bb485e18f..da2a1abd585 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java @@ -32,6 +32,7 @@ import net.fortuna.ical4j.validate.ValidationException; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.quartz.CronExpression; import org.springframework.util.ObjectUtils; @@ -1045,7 +1046,7 @@ public DataValidatorBuilder validateDateAfter(final LocalDate date) { if (this.value != null && date != null) { final LocalDate dateVal = (LocalDate) this.value; - if (date.isAfter(dateVal)) { + if (DateUtils.isAfter(date, dateVal)) { final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") .append(this.parameter).append(".is.less.than.date"); final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter) @@ -1065,7 +1066,7 @@ public DataValidatorBuilder validateDateBefore(final LocalDate date) { if (this.value != null && date != null) { final LocalDate dateVal = (LocalDate) this.value; - if (date.isBefore(dateVal)) { + if (DateUtils.isBefore(date, dateVal)) { final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") .append(this.parameter).append(".is.greater.than.date"); final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter) @@ -1085,7 +1086,7 @@ public DataValidatorBuilder validateDateBeforeOrEqual(final LocalDate date) { if (this.value != null && date != null) { final LocalDate dateVal = (LocalDate) this.value; - if (dateVal.isAfter(date)) { + if (DateUtils.isAfter(dateVal, date)) { final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") .append(this.parameter).append(".is.greater.than.date"); final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter) @@ -1105,7 +1106,7 @@ public DataValidatorBuilder validateDateForEqual(final LocalDate date) { if (this.value != null && date != null) { final LocalDate dateVal = (LocalDate) this.value; - if (!dateVal.isEqual(date)) { + if (!DateUtils.isEqual(dateVal, date)) { final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") .append(this.parameter).append(".is.not.equal.to.date"); final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter) diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableCustom.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableCustom.java index 35ea048b39f..e733975eb8e 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableCustom.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableCustom.java @@ -67,7 +67,7 @@ public void setCreatedBy(final Long createdBy) { @Override public Optional getCreatedDate() { - return null == this.createdDate ? Optional.empty() : Optional.of(this.createdDate); + return Optional.ofNullable(this.createdDate); } @Override @@ -87,7 +87,7 @@ public void setLastModifiedBy(final Long lastModifiedBy) { @Override public Optional getLastModifiedDate() { - return null == this.lastModifiedDate ? Optional.empty() : Optional.of(this.lastModifiedDate); + return Optional.ofNullable(this.lastModifiedDate); } @Override diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java index dec09e6ee74..55beda888e5 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java @@ -25,8 +25,10 @@ import jakarta.persistence.Column; import jakarta.persistence.MappedSuperclass; +import jakarta.validation.constraints.NotNull; import java.time.OffsetDateTime; import java.util.Optional; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -44,7 +46,7 @@ @MappedSuperclass @Getter @Setter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class AbstractAuditableWithUTCDateTimeCustom extends AbstractPersistableCustom implements Auditable { @@ -67,33 +69,36 @@ public abstract class AbstractAuditableWithUTCDateTimeCustom extends AbstractPer private OffsetDateTime lastModifiedDate; @Override + @NotNull public Optional getCreatedBy() { return Optional.ofNullable(this.createdBy); } @Override + @NotNull public Optional getCreatedDate() { - if (this.createdDate == null) { - return Optional.empty(); - } else { - return Optional.of(this.createdDate - .withOffsetSameInstant(DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(this.createdDate.toInstant()))); - } + return Optional.ofNullable(createdDate); + } + + @NotNull + public OffsetDateTime getCreatedDateTime() { + return getCreatedDate().orElseGet(DateUtils::getAuditOffsetDateTime); } @Override + @NotNull public Optional getLastModifiedBy() { return Optional.ofNullable(this.lastModifiedBy); } @Override + @NotNull public Optional getLastModifiedDate() { - if (this.lastModifiedDate == null) { - return Optional.empty(); - } else { - return Optional.of(this.lastModifiedDate - .withOffsetSameInstant(DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(this.lastModifiedDate.toInstant()))); - } + return Optional.ofNullable(lastModifiedDate); } + @NotNull + public OffsetDateTime getLastModifiedDateTime() { + return getLastModifiedDate().orElseGet(DateUtils::getAuditOffsetDateTime); + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java index 5ad239f2369..82a8e7e26d3 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java @@ -22,6 +22,7 @@ import java.time.temporal.ChronoUnit; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.fineract.infrastructure.core.service.DateUtils; public class LocalDateInterval { @@ -66,11 +67,11 @@ public boolean contains(final LocalDate target) { } private boolean isBetweenInclusive(final LocalDate start, final LocalDate end, final LocalDate target) { - return !target.isBefore(start) && !target.isAfter(end); + return !DateUtils.isBefore(target, start) && !DateUtils.isAfter(target, end); } public boolean fallsBefore(final LocalDate dateToCheck) { - return this.endDate.isBefore(dateToCheck); + return DateUtils.isBefore(this.endDate, dateToCheck); } @Override diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessFailedException.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessFailedException.java index a08b2d9b981..0ea52b8fb04 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessFailedException.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessFailedException.java @@ -18,6 +18,9 @@ */ package org.apache.fineract.infrastructure.core.exception; +import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; + +import jakarta.validation.constraints.NotNull; import org.apache.fineract.commands.domain.CommandSource; import org.apache.fineract.commands.domain.CommandWrapper; @@ -28,16 +31,14 @@ public class IdempotentCommandProcessFailedException extends AbstractIdempotentC private final Integer statusCode; - public IdempotentCommandProcessFailedException(CommandWrapper wrapper, CommandSource commandSource) { - super(wrapper.actionName(), wrapper.entityName(), wrapper.getIdempotencyKey(), commandSource.getResult()); - this.statusCode = commandSource.getResultStatusCode(); + public IdempotentCommandProcessFailedException(CommandWrapper wrapper, String idempotencyKey, CommandSource command) { + super(wrapper.actionName(), wrapper.actionName(), idempotencyKey, command.getResult()); + this.statusCode = command.getResultStatusCode(); } + @NotNull public Integer getStatusCode() { // If the database inconsistent we return http 500 instead of null pointer exception - if (statusCode == null) { - return 500; - } - return statusCode; + return statusCode == null ? SC_INTERNAL_SERVER_ERROR : statusCode; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessSucceedException.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessSucceedException.java index d7437275b9b..0542b8b0d8e 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessSucceedException.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessSucceedException.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.infrastructure.core.exception; +import org.apache.fineract.commands.domain.CommandSource; import org.apache.fineract.commands.domain.CommandWrapper; /** @@ -27,9 +28,9 @@ public class IdempotentCommandProcessSucceedException extends AbstractIdempotent private final Integer statusCode; - public IdempotentCommandProcessSucceedException(CommandWrapper wrapper, String response, Integer statusCode) { - super(wrapper.actionName(), wrapper.entityName(), wrapper.getIdempotencyKey(), response); - this.statusCode = statusCode; + public IdempotentCommandProcessSucceedException(CommandWrapper wrapper, String idempotencyKey, CommandSource command) { + super(wrapper.actionName(), wrapper.entityName(), idempotencyKey, command.getResult()); + this.statusCode = command.getResultStatusCode(); } public Integer getStatusCode() { diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessUnderProcessingException.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessUnderProcessingException.java index 4e6b3f61103..7e608e4bef0 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessUnderProcessingException.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessUnderProcessingException.java @@ -25,7 +25,7 @@ */ public class IdempotentCommandProcessUnderProcessingException extends AbstractIdempotentCommandException { - public IdempotentCommandProcessUnderProcessingException(CommandWrapper wrapper) { - super(wrapper.actionName(), wrapper.entityName(), wrapper.getIdempotencyKey(), wrapper.getJson()); + public IdempotentCommandProcessUnderProcessingException(CommandWrapper wrapper, String idempotencyKey) { + super(wrapper.actionName(), wrapper.entityName(), idempotencyKey, wrapper.getJson()); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/ConcurrencyFailureExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/ConcurrencyFailureExceptionMapper.java new file mode 100644 index 00000000000..665a3ef3387 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/ConcurrencyFailureExceptionMapper.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.exceptionmapper; + +import static org.apache.http.HttpStatus.SC_LOCKED; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; +import org.springframework.context.annotation.Scope; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.stereotype.Component; + +/** + * An {@link ExceptionMapper} to map {@link ObjectOptimisticLockingFailureException} thrown by platform into a HTTP API + * friendly format. + */ +@Provider +@Component +@Scope("singleton") +@Slf4j +public class ConcurrencyFailureExceptionMapper implements FineractExceptionMapper, ExceptionMapper { + + @Override + public Response toResponse(final ConcurrencyFailureException exception) { + log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage()); + String type = "unknown"; + String identifier = "unknown"; + if (exception instanceof ObjectOptimisticLockingFailureException olex) { + type = olex.getPersistentClassName(); + identifier = olex.getIdentifier() == null ? null : String.valueOf(olex.getIdentifier()); + } + final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.locked(type, identifier); + return Response.status(SC_LOCKED).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build(); + } + + @Override + public int errorCode() { + return 4009; + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandExceptionMapper.java new file mode 100644 index 00000000000..4facc0976d6 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandExceptionMapper.java @@ -0,0 +1,70 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.exceptionmapper; + +import static org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException; +import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException; +import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException; +import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException; +import org.eclipse.persistence.exceptions.OptimisticLockException; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * An {@link ExceptionMapper} to map {@link OptimisticLockException} thrown by platform into a HTTP API friendly format. + */ +@Provider +@Component +@Scope("singleton") +@Slf4j +public class IdempotentCommandExceptionMapper implements FineractExceptionMapper, ExceptionMapper { + + @Override + public Response toResponse(final AbstractIdempotentCommandException exception) { + log.warn("Processing {} request: {}", exception.getClass().getName(), exception.getMessage()); + Status status = null; + if (exception instanceof IdempotentCommandProcessSucceedException pse) { + Integer statusCode = pse.getStatusCode(); + status = statusCode == null ? Status.OK : Status.fromStatusCode(statusCode); + } + if (exception instanceof IdempotentCommandProcessUnderProcessingException) { + status = Status.CONFLICT; + } else if (exception instanceof IdempotentCommandProcessFailedException pfe) { + status = Status.fromStatusCode(pfe.getStatusCode()); + } + if (status == null) { + status = Status.INTERNAL_SERVER_ERROR; + } + return Response.status(status).entity(exception.getResponse()).header(IDEMPOTENT_CACHE_HEADER, "true") + .type(MediaType.APPLICATION_JSON).build(); + } + + @Override + public int errorCode() { + return 4209; + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessFailedExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessFailedExceptionMapper.java deleted file mode 100644 index 88e350b193d..00000000000 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessFailedExceptionMapper.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.exceptionmapper; - -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.Status; -import jakarta.ws.rs.ext.ExceptionMapper; -import jakarta.ws.rs.ext.Provider; -import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException; -import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException; -import org.springframework.stereotype.Component; - -@Provider -@Component -@Slf4j -public class IdempotentCommandProcessFailedExceptionMapper implements ExceptionMapper { - - @Override - public Response toResponse(final IdempotentCommandProcessFailedException exception) { - log.debug("Idempotent processing failed request: {}", exception.getMessage()); - Status statusCode = Status.fromStatusCode(exception.getStatusCode()); - return Response.status(statusCode).entity(exception.getResponse()) - .header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER, "true").type(MediaType.APPLICATION_JSON).build(); - } -} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessSucceedExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessSucceedExceptionMapper.java deleted file mode 100644 index 77916eb541b..00000000000 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessSucceedExceptionMapper.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.exceptionmapper; - -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.ext.ExceptionMapper; -import jakarta.ws.rs.ext.Provider; -import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException; -import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException; -import org.springframework.stereotype.Component; - -@Provider -@Component -@Slf4j -public class IdempotentCommandProcessSucceedExceptionMapper implements ExceptionMapper { - - @Override - public Response toResponse(final IdempotentCommandProcessSucceedException exception) { - log.debug("Idempotent processing success request: {}", exception.getMessage()); - Response.ResponseBuilder responseBuilder = Response.status(exception.getStatusCode()); - if (exception.getResponse() != null) { - responseBuilder.entity(exception.getResponse()); - } - return responseBuilder.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER, "true").type(MediaType.APPLICATION_JSON) - .build(); - } -} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessUnderProcessingExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessUnderProcessingExceptionMapper.java deleted file mode 100644 index dbaaea466e7..00000000000 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandProcessUnderProcessingExceptionMapper.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.exceptionmapper; - -import static org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER; - -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.Status; -import jakarta.ws.rs.ext.ExceptionMapper; -import jakarta.ws.rs.ext.Provider; -import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException; -import org.springframework.stereotype.Component; - -@Provider -@Component -@Slf4j -public class IdempotentCommandProcessUnderProcessingExceptionMapper - implements ExceptionMapper { - - @Override - public Response toResponse(final IdempotentCommandProcessUnderProcessingException exception) { - log.debug("Idempotent under processing request: {}", exception.getMessage()); - return Response.status(Status.CONFLICT).entity(exception.getResponse()).header(IDEMPOTENT_CACHE_HEADER, "true") - .type(MediaType.APPLICATION_JSON).build(); - } -} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OptimisticLockExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OptimisticLockExceptionMapper.java new file mode 100644 index 00000000000..b4af9e3ff53 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OptimisticLockExceptionMapper.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.exceptionmapper; + +import static org.apache.http.HttpStatus.SC_LOCKED; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; +import org.eclipse.persistence.exceptions.OptimisticLockException; +import org.springframework.context.annotation.Scope; +import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.stereotype.Component; + +/** + * An {@link ExceptionMapper} to map {@link ObjectOptimisticLockingFailureException} thrown by platform into a HTTP API + * friendly format. + */ +@Provider +@Component +@Scope("singleton") +@Slf4j +public class OptimisticLockExceptionMapper implements FineractExceptionMapper, ExceptionMapper { + + @Override + public Response toResponse(final OptimisticLockException exception) { + log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage()); + String type = exception.getQuery() == null ? "unknown" : "query"; + String identifier = "unknown"; + final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.locked(type, identifier); + return Response.status(SC_LOCKED).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build(); + } + + @Override + public int errorCode() { + return 4009; + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreBatchFilter.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreBatchFilter.java index b2e49222a1b..1af80b65b3f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreBatchFilter.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreBatchFilter.java @@ -43,9 +43,9 @@ public BatchResponse doFilter(BatchRequest batchRequest, UriInfo uriInfo, BatchF .setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, idempotentKey)); BatchResponse result = chain.serviceCall(batchRequest, uriInfo); Optional commandId = helper.getCommandId(null); - boolean isSuccessWithoutStored = helper.isStoreIdempotencyKey(null) && commandId.isPresent(); + boolean isSuccessWithoutStored = commandId.isPresent() && helper.isStoreIdempotencyKey(null); if (isSuccessWithoutStored) { - helper.storeCommandResult(true, result.getStatusCode(), result.getBody(), commandId); + helper.storeCommandResult(result.getStatusCode(), result.getBody(), commandId.get()); } return result; } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java index 2537a6b9b2f..daadb63b922 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java @@ -56,12 +56,12 @@ protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull Ht filterChain.doFilter(request, wrapper.getValue() != null ? wrapper.getValue() : response); Optional commandId = helper.getCommandId(request); - boolean isSuccessWithoutStored = helper.isStoreIdempotencyKey(request) && commandId.isPresent() - && helper.isAllowedContentTypeResponse(response) && wrapper.getValue() != null; + boolean isSuccessWithoutStored = commandId.isPresent() && wrapper.getValue() != null && helper.isStoreIdempotencyKey(request) + && helper.isAllowedContentTypeResponse(response); if (isSuccessWithoutStored) { - helper.storeCommandResult(false, response.getStatus(), Optional.ofNullable(wrapper.getValue()) + helper.storeCommandResult(response.getStatus(), Optional.ofNullable(wrapper.getValue()) .map(ContentCachingResponseWrapper::getContentAsByteArray).map(s -> new String(s, StandardCharsets.UTF_8)).orElse(null), - commandId); + commandId.get()); } if (wrapper.getValue() != null) { wrapper.getValue().copyBodyToResponse(); diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java index 5ca5147a038..f2670fec4f0 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java @@ -25,6 +25,7 @@ import org.apache.fineract.commands.domain.CommandSourceRepository; import org.apache.fineract.commands.service.CommandSourceService; import org.apache.fineract.commands.service.SynchronousCommandProcessingService; +import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; import org.springframework.stereotype.Component; @@ -36,15 +37,13 @@ public class IdempotencyStoreHelper { private final CommandSourceService commandSourceService; private final FineractRequestContextHolder fineractRequestContextHolder; - public void storeCommandResult(boolean batch, int response, String body, Optional commandId) { - commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> { + public void storeCommandResult(Integer response, String body, Long commandId) { + commandSourceRepository.findById(commandId).ifPresent(commandSource -> { + boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent(); commandSource.setResultStatusCode(response); commandSource.setResult(body); - if (batch) { - commandSourceService.saveResultNoTransaction(commandSource); - } else { - commandSourceService.saveResult(commandSource); - } + commandSource = sameTransaction ? commandSourceService.saveResultSameTransaction(commandSource) + : commandSourceService.saveResultNewTransaction(commandSource); }); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java index 1056bde387b..d706e8fd514 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java @@ -20,6 +20,7 @@ import static java.time.temporal.ChronoUnit.DAYS; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; @@ -33,7 +34,6 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.jetbrains.annotations.NotNull; public final class DateUtils { @@ -46,6 +46,8 @@ private DateUtils() { } + // DateTime + public static ZoneId getSystemZoneId() { return ZoneId.systemDefault(); } @@ -56,27 +58,34 @@ public static ZoneId getDateTimeZoneOfTenant() { } public static LocalDate getLocalDateOfTenant() { - final ZoneId zone = getDateTimeZoneOfTenant(); - return LocalDate.now(zone); + return LocalDate.now(getDateTimeZoneOfTenant()); } public static LocalDateTime getLocalDateTimeOfTenant() { - final ZoneId zone = getDateTimeZoneOfTenant(); - return LocalDateTime.now(zone).truncatedTo(ChronoUnit.SECONDS); + return getLocalDateTimeOfTenant(null); + } + + public static LocalDateTime getLocalDateTimeOfTenant(ChronoUnit truncate) { + LocalDateTime now = LocalDateTime.now(getDateTimeZoneOfTenant()); + return truncate == null ? now : now.truncatedTo(truncate); } public static OffsetDateTime getOffsetDateTimeOfTenant() { - final ZoneId zone = getDateTimeZoneOfTenant(); - return OffsetDateTime.now(zone).truncatedTo(ChronoUnit.SECONDS); + return getOffsetDateTimeOfTenant(null); } - public static OffsetDateTime getOffsetDateTimeOfTenantWithMostPrecision() { - final ZoneId zone = getDateTimeZoneOfTenant(); - return OffsetDateTime.now(zone); + public static OffsetDateTime getOffsetDateTimeOfTenant(ChronoUnit truncate) { + OffsetDateTime now = OffsetDateTime.now(getDateTimeZoneOfTenant()); + return truncate == null ? now : now.truncatedTo(truncate); } public static LocalDateTime getLocalDateTimeOfSystem() { - return LocalDateTime.now(ZoneId.systemDefault()).truncatedTo(ChronoUnit.SECONDS); + return getLocalDateTimeOfSystem(null); + } + + public static LocalDateTime getLocalDateTimeOfSystem(ChronoUnit truncate) { + LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault()); + return truncate == null ? now : now.truncatedTo(truncate); } public static LocalDateTime getAuditLocalDateTime() { @@ -87,22 +96,178 @@ public static OffsetDateTime getAuditOffsetDateTime() { return OffsetDateTime.now(ZoneOffset.UTC); } - public static boolean isDateInTheFuture(final LocalDate localDate) { - return localDate.isAfter(getBusinessLocalDate()); + public static int compare(LocalDateTime first, LocalDateTime second) { + return compare(first, second, null); } - public static LocalDate getBusinessLocalDate() { - return ThreadLocalContextUtil.getBusinessDate(); + public static int compare(LocalDateTime first, LocalDateTime second, ChronoUnit truncate) { + if (first == null) { + return second == null ? 0 : -1; + } + if (second == null) { + return 1; + } + return truncate == null ? first.compareTo(second) : first.truncatedTo(truncate).compareTo(second.truncatedTo(truncate)); } - public static long getDifferenceInDays(final LocalDate localDateBefore, final LocalDate localDateAfter) { - return DAYS.between(localDateBefore, localDateAfter); + public static boolean isEqual(LocalDateTime first, LocalDateTime second) { + return isEqual(first, second, null); + } + + public static boolean isEqual(LocalDateTime first, LocalDateTime second, ChronoUnit truncate) { + return compare(first, second, truncate) == 0; + } + + public static boolean isEqualTenantDateTime(LocalDateTime dateTime) { + return isEqualTenantDateTime(dateTime, null); + } + + public static boolean isEqualTenantDateTime(LocalDateTime dateTime, ChronoUnit truncate) { + return isEqual(dateTime, getLocalDateTimeOfTenant(), truncate); + } + + public static boolean isEqualSystemDateTime(LocalDateTime dateTime) { + return isEqualSystemDateTime(dateTime, null); + } + + public static boolean isEqualSystemDateTime(LocalDateTime dateTime, ChronoUnit truncate) { + return isEqual(dateTime, getLocalDateTimeOfSystem(), truncate); + } + + public static boolean isBefore(LocalDateTime first, LocalDateTime second) { + return isBefore(first, second, null); + } + + public static boolean isBefore(LocalDateTime first, LocalDateTime second, ChronoUnit truncate) { + return compare(first, second, truncate) < 0; + } + + public static boolean isBeforeTenantDateTime(LocalDateTime dateTime) { + return isBeforeTenantDateTime(dateTime, null); + } + + public static boolean isBeforeTenantDateTime(LocalDateTime dateTime, ChronoUnit truncate) { + return isBefore(dateTime, getLocalDateTimeOfTenant(), truncate); + } + + public static boolean isBeforeSystemDateTime(LocalDateTime dateTime) { + return isBeforeSystemDateTime(dateTime, null); + } + + public static boolean isBeforeSystemDateTime(LocalDateTime dateTime, ChronoUnit truncate) { + return isBefore(dateTime, getLocalDateTimeOfSystem(), truncate); + } + + public static boolean isAfter(LocalDateTime first, LocalDateTime second) { + return isAfter(first, second, null); + } + + public static boolean isAfter(LocalDateTime first, LocalDateTime second, ChronoUnit truncate) { + return compare(first, second, truncate) > 0; + } + + public static boolean isAfterTenantDateTime(LocalDateTime dateTime) { + return isAfterTenantDateTime(dateTime, null); + } + + public static boolean isAfterTenantDateTime(LocalDateTime dateTime, ChronoUnit truncate) { + return isAfter(dateTime, getLocalDateTimeOfTenant(), truncate); + } + + public static boolean isAfterSystemDateTime(LocalDateTime dateTime) { + return isAfterSystemDateTime(dateTime, null); + } + + public static boolean isAfterSystemDateTime(LocalDateTime dateTime, ChronoUnit truncate) { + return isAfter(dateTime, getLocalDateTimeOfSystem(), truncate); + } + + public static int compare(OffsetDateTime first, OffsetDateTime second) { + return compare(first, second, null); + } + + public static int compare(OffsetDateTime first, OffsetDateTime second, ChronoUnit truncate) { + if (first == null) { + return second == null ? 0 : -1; + } + if (second == null) { + return 1; + } + first = first.withOffsetSameInstant(ZoneOffset.UTC); + second = second.withOffsetSameInstant(ZoneOffset.UTC); + return truncate == null ? first.compareTo(second) : first.truncatedTo(truncate).compareTo(second.truncatedTo(truncate)); + } + + public static boolean isEqual(OffsetDateTime first, OffsetDateTime second) { + return isEqual(first, second, null); + } + + public static boolean isEqual(OffsetDateTime first, OffsetDateTime second, ChronoUnit truncate) { + return compare(first, second, truncate) == 0; + } + + public static boolean isEqualTenantDateTime(OffsetDateTime dateTime) { + return isEqualTenantDateTime(dateTime, null); + } + + public static boolean isEqualTenantDateTime(OffsetDateTime dateTime, ChronoUnit truncate) { + return isEqual(dateTime, getOffsetDateTimeOfTenant(), truncate); + } + + public static boolean isBefore(OffsetDateTime first, OffsetDateTime second) { + return isBefore(first, second, null); + } + + public static boolean isBefore(OffsetDateTime first, OffsetDateTime second, ChronoUnit truncate) { + return compare(first, second, truncate) < 0; + } + + public static boolean isBeforeTenantDateTime(OffsetDateTime dateTime) { + return isBeforeTenantDateTime(dateTime, null); + } + + public static boolean isBeforeTenantDateTime(OffsetDateTime dateTime, ChronoUnit truncate) { + return isBefore(dateTime, getOffsetDateTimeOfTenant(), truncate); + } + + public static boolean isAfter(OffsetDateTime first, OffsetDateTime second) { + return isAfter(first, second, null); + } + + public static boolean isAfter(OffsetDateTime first, OffsetDateTime second, ChronoUnit truncate) { + return compare(first, second, truncate) > 0; + } + + public static boolean isAfterTenantDateTime(OffsetDateTime dateTime) { + return isAfterTenantDateTime(dateTime, null); + } + + public static boolean isAfterTenantDateTime(OffsetDateTime dateTime, ChronoUnit truncate) { + return isAfter(dateTime, getOffsetDateTimeOfTenant(), truncate); + } + + // Date + + public static LocalDate getBusinessLocalDate() { + return ThreadLocalContextUtil.getBusinessDate(); } public static int compareToBusinessDate(LocalDate date) { return compare(date, getBusinessLocalDate()); } + public static boolean isEqualTenantDate(LocalDate date) { + return isEqual(date, getLocalDateOfTenant()); + } + + public static boolean isBeforeTenantDate(LocalDate date) { + return isBefore(date, getLocalDateOfTenant()); + } + + public static boolean isAfterTenantDate(LocalDate date) { + return isAfter(date, getLocalDateOfTenant()); + } + public static boolean isEqualBusinessDate(LocalDate date) { return isEqual(date, getBusinessLocalDate()); } @@ -115,8 +280,12 @@ public static boolean isAfterBusinessDate(LocalDate date) { return isAfter(date, getBusinessLocalDate()); } + public static boolean isDateInTheFuture(final LocalDate localDate) { + return isAfterBusinessDate(localDate); + } + public static int compare(LocalDate first, LocalDate second) { - return first == null ? (second == null ? 0 : -1) : first.compareTo(second); + return first == null ? (second == null ? 0 : -1) : (second == null ? 1 : first.compareTo(second)); } public static boolean isEqual(LocalDate first, LocalDate second) { @@ -131,6 +300,12 @@ public static boolean isAfter(LocalDate first, LocalDate second) { return first != null && (second == null || first.isAfter(second)); } + public static long getDifferenceInDays(final LocalDate localDateBefore, final LocalDate localDateAfter) { + return DAYS.between(localDateBefore, localDateAfter); + } + + // Parse, format + public static LocalDate parseLocalDate(String stringDate) { return parseLocalDate(stringDate, null); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java index 443ea7ca4ca..ba58b25f73a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java @@ -450,4 +450,21 @@ public static BigDecimal percentageOf(final BigDecimal value, final BigDecimal p } return percentageOf; } + + /** + * Calculate percentage of a value + * + * @param value + * @param percentage + * @param mc + * @return + */ + public static BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage, final MathContext mc) { + BigDecimal percentageOf = BigDecimal.ZERO; + if (isGreaterThanZero(value)) { + final BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100L), mc); + percentageOf = value.multiply(multiplicand, mc); + } + return percentageOf; + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java index 36d13697e25..60f7aed1a85 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java @@ -23,8 +23,10 @@ import jakarta.validation.constraints.NotNull; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Sort; @@ -251,18 +253,33 @@ public String buildOrderBy(List orders, String alias, boolean embedd .collect(Collectors.joining(", ")); } - public String buildInsert(@NotNull String definition, Collection fields) { + public String buildInsert(@NotNull String definition, List fields, Map headers) { if (fields == null || fields.isEmpty()) { return ""; } return "INSERT INTO " + escape(definition) + '(' + fields.stream().map(this::escape).collect(Collectors.joining(", ")) - + ") VALUES (?" + ", ?".repeat(fields.size() - 1) + ')'; + + ") VALUES (" + fields.stream().map(e -> decoratePlaceHolder(headers, e, "?")).collect(Collectors.joining(", ")) + ')'; } - public String buildUpdate(@NotNull String definition, Collection fields) { + public String buildUpdate(@NotNull String definition, List fields, Map headers) { if (fields == null || fields.isEmpty()) { return ""; } - return "UPDATE " + escape(definition) + " SET " + fields.stream().map(e -> escape(e) + " = ?").collect(Collectors.joining(", ")); + return "UPDATE " + escape(definition) + " SET " + + fields.stream().map(e -> escape(e) + " = " + decoratePlaceHolder(headers, e, "?")).collect(Collectors.joining(", ")); + } + + private String decoratePlaceHolder(Map headers, String field, String placeHolder) { + DatabaseType dialect = getDialect(); + if (dialect.isPostgres()) { + ResultsetColumnHeaderData header = headers.get(field); + if (header != null) { + JdbcJavaType columnType = header.getColumnType(); + if (columnType.isJsonType()) { + return placeHolder + "::" + columnType.getJdbcName(dialect); + } + } + } + return placeHolder; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JdbcJavaType.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JdbcJavaType.java index a03834de0e0..aea1339f6c4 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JdbcJavaType.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JdbcJavaType.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList; import jakarta.validation.constraints.NotNull; +import java.io.Serializable; import java.sql.JDBCType; import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; @@ -73,6 +74,7 @@ public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) { TINYTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "TINYTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), // MEDIUMTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "MEDIUMTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), // LONGTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "LONGTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), // + JSON(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "JSON"), new DialectType(JDBCType.VARCHAR, "JSON")), // DATE(JavaType.LOCAL_DATE, new DialectType(JDBCType.DATE), new DialectType(JDBCType.DATE)), // // precision for TIME, TIMESTAMP (postgres) and INTERVAL specifies the number of fractional digits retained in the // seconds field, but by default, there is no explicit bound on precision @@ -125,7 +127,7 @@ public JavaType getJavaType() { return javaType; } - public static JdbcJavaType getByTypeName(@NotNull DatabaseType dialect, String name) { + public static JdbcJavaType getByTypeName(@NotNull DatabaseType dialect, String name, boolean check) { if (name == null) { return null; } @@ -144,6 +146,10 @@ public static JdbcJavaType getByTypeName(@NotNull DatabaseType dialect, String n } } } + if (check) { + throw new PlatformServiceUnavailableException("error.msg.database.type.not.supported", + "Data type '" + name + "' is not supported "); + } return null; } @@ -178,7 +184,11 @@ public boolean isVarcharType() { } public boolean isTextType() { - return this == TEXT || this == TINYTEXT || this == MEDIUMTEXT || this == LONGTEXT; + return this == TEXT || this == TINYTEXT || this == MEDIUMTEXT || this == LONGTEXT || this == JSON; + } + + public boolean isJsonType() { + return this == JSON; } public boolean isSerialType() { @@ -261,7 +271,7 @@ public String formatSql(@NotNull DatabaseType dialect, Integer precision, Intege public Object toJdbcValue(@NotNull DatabaseType dialect, Object value, boolean check) { if (value != null && check && !javaType.getObjectType().matchType(value.getClass(), false)) { - throw new PlatformServiceUnavailableException("error.msg.database.type.not.allowed", + throw new PlatformServiceUnavailableException("error.msg.database.type.not.valid", "Data type of parameter " + value + " does not match " + this); } return toJdbcValueImpl(dialect, value); @@ -272,7 +282,7 @@ public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) { } @com.google.errorprone.annotations.Immutable - private static final class DialectType { + private static final class DialectType implements Serializable { @NotNull private final JDBCType jdbcType; diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java index a1b93080737..a28c92a91fe 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java @@ -75,7 +75,7 @@ private ResultsetColumnHeaderData(final String columnName, String columnType, fi this.isColumnIndexed = columnIsIndexed; // Refer org.drizzle.jdbc.internal.mysql.MySQLType.java - this.columnType = JdbcJavaType.getByTypeName(dialect, adjustColumnType(columnType)); + this.columnType = JdbcJavaType.getByTypeName(dialect, adjustColumnType(columnType), true); this.columnDisplayType = calcDisplayType(); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTasklet.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTasklet.java index 4217620ac4f..ac9d01a2553 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTasklet.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTasklet.java @@ -99,7 +99,7 @@ private void sendEventsToProducer(Map> partitions) { } private void markEventsAsSent(List eventIds) { - OffsetDateTime sentAt = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); + OffsetDateTime sentAt = DateUtils.getAuditOffsetDateTime(); // Partitioning dataset to avoid exception: PreparedStatement can have at most 65,535 parameters List> partitions = Lists.partition(eventIds, 5_000); diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEvent.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEvent.java index d281facac46..b3ff59f189b 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEvent.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEvent.java @@ -80,7 +80,7 @@ public ExternalEvent(String type, String category, String schema, byte[] data, S this.data = data; this.idempotencyKey = idempotencyKey; this.aggregateRootId = aggregateRootId; - this.createdAt = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); + this.createdAt = DateUtils.getAuditOffsetDateTime(); this.status = ExternalEventStatus.TO_BE_SENT; this.businessDate = DateUtils.getBusinessLocalDate(); } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java b/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java index 92401cd4ecc..ad912c4955a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java @@ -20,6 +20,7 @@ import java.time.LocalDate; import java.util.List; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; @@ -30,47 +31,29 @@ private HolidayUtil() { } public static LocalDate getRepaymentRescheduleDateToIfHoliday(LocalDate repaymentDate, final List holidays) { - - for (final Holiday holiday : holidays) { - if (repaymentDate.equals(holiday.getFromDate()) || repaymentDate.equals(holiday.getToDate()) - || (repaymentDate.isAfter(holiday.getFromDate()) && repaymentDate.isBefore(holiday.getToDate()))) { - repaymentDate = getRepaymentRescheduleDateIfHoliday(repaymentDate, holidays); - } - } - return repaymentDate; - } - - private static LocalDate getRepaymentRescheduleDateIfHoliday(final LocalDate repaymentDate, final List holidays) { - for (final Holiday holiday : holidays) { - if (repaymentDate.equals(holiday.getFromDate()) || repaymentDate.equals(holiday.getToDate()) - || (repaymentDate.isAfter(holiday.getFromDate()) && repaymentDate.isBefore(holiday.getToDate()))) { - // should be take from holiday - return holiday.getRepaymentsRescheduledTo(); + if (isHoliday(repaymentDate, holiday)) { + repaymentDate = holiday.getRepaymentsRescheduledTo(); } } return repaymentDate; } public static boolean isHoliday(final LocalDate date, final List holidays) { - for (final Holiday holiday : holidays) { - if (date.isEqual(holiday.getFromDate()) || date.isEqual(holiday.getToDate()) - || (date.isAfter(holiday.getFromDate()) && date.isBefore(holiday.getToDate()))) { - return true; - } - } - - return false; + return getApplicableHoliday(date, holidays) != null; } public static Holiday getApplicableHoliday(final LocalDate repaymentDate, final List holidays) { - Holiday referedHoliday = null; for (final Holiday holiday : holidays) { - if (!repaymentDate.isBefore(holiday.getFromDate()) && !repaymentDate.isAfter(holiday.getToDate())) { - referedHoliday = holiday; + if (isHoliday(repaymentDate, holiday)) { + return holiday; } } - return referedHoliday; + return null; + } + + public static boolean isHoliday(LocalDate date, Holiday holiday) { + return !DateUtils.isBefore(date, holiday.getFromDate()) && !DateUtils.isAfter(date, holiday.getToDate()); } public static void updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(final AdjustedDateDetailsDTO adjustedDateDetailsDTO, diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java index 6f955882e13..c7e290af69f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java @@ -120,6 +120,24 @@ public static double roundToMultiplesOf(final double existingVal, final Integer return amountScaled; } + public static BigDecimal roundToMultiplesOf(final BigDecimal existingVal, final Integer inMultiplesOf) { + BigDecimal amountScaled = existingVal; + BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf.intValue()); + if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) { + amountScaled = existingVal.divide(inMultiplesOfValue, 0, RoundingMode.HALF_UP).multiply(inMultiplesOfValue); + } + return amountScaled; + } + + public static Money roundToMultiplesOf(final Money existingVal, final Integer inMultiplesOf) { + BigDecimal amountScaled = existingVal.getAmount(); + BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf.intValue()); + if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) { + amountScaled = amountScaled.divide(inMultiplesOfValue, 0, RoundingMode.HALF_UP).multiply(inMultiplesOfValue); + } + return Money.of(existingVal.getCurrency(), amountScaled); + } + public static double ceiling(final double n, final double s) { double c; diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/office/domain/Office.java b/fineract-core/src/main/java/org/apache/fineract/organisation/office/domain/Office.java index 29c61c1f5ed..f6bd289c553 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/office/domain/Office.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/office/domain/Office.java @@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.organisation.office.exception.CannotUpdateOfficeWithParentOfficeSameAsSelf; import org.apache.fineract.organisation.office.exception.RootOfficeParentCannotBeUpdated; @@ -155,11 +156,11 @@ public Map update(final JsonCommand command) { } public boolean isOpeningDateBefore(final LocalDate baseDate) { - return getOpeningLocalDate().isBefore(baseDate); + return DateUtils.isBefore(getOpeningLocalDate(), baseDate); } - public boolean isOpeningDateAfter(final LocalDate activationLocalDate) { - return getOpeningLocalDate().isAfter(activationLocalDate); + public boolean isOpeningDateAfter(final LocalDate baseDate) { + return DateUtils.isAfter(getOpeningLocalDate(), baseDate); } public LocalDate getOpeningLocalDate() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java index 9a5211a4721..8426c3b971d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java @@ -28,6 +28,7 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy; import org.apache.fineract.portfolio.calendar.domain.CalendarType; @@ -377,12 +378,11 @@ private CalendarData(final Long id, final Long calendarInstanceId, final Long en } public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) { - return this.startDate != null && compareDate != null - && (this.startDate.isBefore(compareDate) || this.startDate.equals(compareDate)); + return this.startDate != null && compareDate != null && !DateUtils.isAfter(this.startDate, compareDate); } public boolean isEndDateAfterOrEqual(final LocalDate compareDate) { - return this.endDate != null && compareDate != null && (this.endDate.isAfter(compareDate) || this.endDate.isEqual(compareDate)); + return this.endDate != null && compareDate != null && !DateUtils.isBefore(this.endDate, compareDate); } public boolean isBetweenStartAndEndDate(final LocalDate compareDate) { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarHistoryDataWrapper.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarHistoryDataWrapper.java index c0568c84541..a932ceaeb46 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarHistoryDataWrapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarHistoryDataWrapper.java @@ -20,10 +20,10 @@ import java.time.LocalDate; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.calendar.domain.CalendarHistory; public class CalendarHistoryDataWrapper { @@ -37,16 +37,16 @@ public CalendarHistoryDataWrapper(final Set calendarHistoryList @Override public int compare(CalendarHistory calendarHistory1, CalendarHistory calendarHistory2) { - return calendarHistory1.getEndDateLocalDate().compareTo(calendarHistory2.getEndDateLocalDate()); + return DateUtils.compare(calendarHistory1.getEndDate(), calendarHistory2.getEndDate()); } }; - Collections.sort(this.calendarHistoryList, orderByDate); + this.calendarHistoryList.sort(orderByDate); } public CalendarHistory getCalendarHistory(final LocalDate dueRepaymentPeriodDate) { CalendarHistory calendarHistory = null; for (CalendarHistory history : this.calendarHistoryList) { - if (history.getEndDateLocalDate().isAfter(dueRepaymentPeriodDate)) { + if (DateUtils.isAfter(history.getEndDate(), dueRepaymentPeriodDate)) { calendarHistory = history; break; } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java index ed8419b63f7..227bc191704 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java @@ -175,23 +175,19 @@ public static Calendar fromJson(final JsonCommand command) { } public Map updateStartDateAndDerivedFeilds(final LocalDate newMeetingStartDate) { - final Map actualChanges = new LinkedHashMap<>(9); final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); - - if (newMeetingStartDate.isBefore(currentDate)) { + if (DateUtils.isBefore(newMeetingStartDate, currentDate)) { final String defaultUserMessage = "New meeting effective from date cannot be in past"; throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage, newMeetingStartDate, getStartDateLocalDate()); } else if (isStartDateAfter(newMeetingStartDate) && isStartDateBeforeOrEqual(currentDate)) { - // new meeting date should be on or after start date or current - // date + // new meeting date should be on or after start date or current date final String defaultUserMessage = "New meeting effective from date cannot be a date before existing meeting start date"; throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newMeetingStartDate, getStartDateLocalDate()); } else { - actualChanges.put(CalendarSupportedParameters.START_DATE.getValue(), newMeetingStartDate.toString()); this.startDate = newMeetingStartDate; @@ -200,33 +196,25 @@ public Map updateStartDateAndDerivedFeilds(final LocalDate newMe * recurring day and update it if it is changed. For weekly type is weekday and for monthly type it is day * of the month */ - CalendarFrequencyType calendarFrequencyType = CalendarUtils.getFrequency(this.recurrence); Integer interval = CalendarUtils.getInterval(this.recurrence); Integer repeatsOnDay = null; - /* - * Repeats on day, need to derive based on the start date - */ - + // Repeats on day, need to derive based on the start date if (calendarFrequencyType.isWeekly()) { repeatsOnDay = newMeetingStartDate.get(ChronoField.DAY_OF_WEEK); } else if (calendarFrequencyType.isMonthly()) { repeatsOnDay = newMeetingStartDate.getDayOfMonth(); } - // TODO cover other recurrence also this.recurrence = constructRecurrence(calendarFrequencyType, interval, repeatsOnDay, null); - } return actualChanges; - } public Map update(final JsonCommand command, final Boolean areActiveEntitiesSynced) { - final Map actualChanges = new LinkedHashMap<>(9); if (command.isChangeInStringParameterNamed(CalendarSupportedParameters.TITLE.getValue(), this.title)) { @@ -234,13 +222,11 @@ public Map update(final JsonCommand command, final Boolean areAc actualChanges.put(CalendarSupportedParameters.TITLE.getValue(), newValue); this.title = StringUtils.defaultIfEmpty(newValue, null); } - if (command.isChangeInStringParameterNamed(CalendarSupportedParameters.DESCRIPTION.getValue(), this.description)) { final String newValue = command.stringValueOfParameterNamed(CalendarSupportedParameters.DESCRIPTION.getValue()); actualChanges.put(CalendarSupportedParameters.DESCRIPTION.getValue(), newValue); this.description = StringUtils.defaultIfEmpty(newValue, null); } - if (command.isChangeInStringParameterNamed(CalendarSupportedParameters.LOCATION.getValue(), this.location)) { final String newValue = command.stringValueOfParameterNamed(CalendarSupportedParameters.LOCATION.getValue()); actualChanges.put(CalendarSupportedParameters.LOCATION.getValue(), newValue); @@ -251,12 +237,11 @@ public Map update(final JsonCommand command, final Boolean areAc final String localeAsInput = command.locale(); final String startDateParamName = CalendarSupportedParameters.START_DATE.getValue(); if (command.isChangeInLocalDateParameterNamed(startDateParamName, getStartDateLocalDate())) { - final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName); final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName); final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); - if (newValue.isBefore(currentDate)) { + if (DateUtils.isBefore(newValue, currentDate)) { final String defaultUserMessage = "New meeting effective from date cannot be in past"; throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage, newValue, getStartDateLocalDate()); } else if (isStartDateAfter(newValue) && isStartDateBeforeOrEqual(currentDate)) { @@ -472,24 +457,19 @@ public Set history() { } public boolean isStartDateBefore(final LocalDate compareDate) { - if (this.startDate != null && compareDate != null && getStartDateLocalDate().isBefore(compareDate)) { - return true; - } - return false; + return startDate != null && DateUtils.isBefore(startDate, compareDate); } public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) { - return this.startDate != null && compareDate != null - && (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)); + return startDate != null && !DateUtils.isAfter(startDate, compareDate); } public boolean isStartDateAfter(final LocalDate compareDate) { - return this.startDate != null && compareDate != null && getStartDateLocalDate().isAfter(compareDate); + return compareDate != null && DateUtils.isAfter(startDate, compareDate); } public boolean isEndDateAfterOrEqual(final LocalDate compareDate) { - return this.endDate != null && compareDate != null - && (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)); + return compareDate != null && !DateUtils.isBefore(endDate, compareDate); } public boolean isBetweenStartAndEndDate(final LocalDate compareDate) { @@ -585,7 +565,7 @@ public boolean isValidRecurringDate(final LocalDate compareDate, Boolean isSkipR // validate with history details. for (CalendarHistory history : history()) { if (history.isBetweenStartAndEndDate(compareDate)) { - return CalendarUtils.isValidRedurringDate(history.getRecurrence(), history.getStartDateLocalDate(), compareDate, + return CalendarUtils.isValidRedurringDate(history.getRecurrence(), history.getStartDate(), compareDate, isSkipRepaymentOnFirstMonth, numberOfDays); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarEntityType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarEntityType.java index f65593dd891..e487c925363 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarEntityType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarEntityType.java @@ -88,51 +88,63 @@ public String toString() { } public static boolean isGroup(final Integer value) { - return CalendarEntityType.GROUPS.value.equals(value) ? true : false; + return CalendarEntityType.GROUPS.value.equals(value); } public static boolean isGroup(final String name) { - return CalendarEntityType.GROUPS.name().equalsIgnoreCase(name) ? true : false; + return CalendarEntityType.GROUPS.name().equalsIgnoreCase(name); } public static boolean isCenter(final Integer value) { - return CalendarEntityType.CENTERS.value.equals(value) ? true : false; + return CalendarEntityType.CENTERS.value.equals(value); } public static boolean isCenter(final String name) { - return CalendarEntityType.CENTERS.name().equalsIgnoreCase(name) ? true : false; + return CalendarEntityType.CENTERS.name().equalsIgnoreCase(name); } public static boolean isLoan(final Integer value) { - return CalendarEntityType.LOANS.value.equals(value) ? true : false; + return CalendarEntityType.LOANS.value.equals(value); } public static boolean isLoan(final String name) { - return CalendarEntityType.LOANS.name().equalsIgnoreCase(name) ? true : false; + return CalendarEntityType.LOANS.name().equalsIgnoreCase(name); } public static boolean isClient(final Integer value) { - return CalendarEntityType.CLIENTS.value.equals(value) ? true : false; + return CalendarEntityType.CLIENTS.value.equals(value); } public static boolean isClient(final String name) { - return CalendarEntityType.CLIENTS.name().equalsIgnoreCase(name) ? true : false; + return CalendarEntityType.CLIENTS.name().equalsIgnoreCase(name); + } + + public static boolean isSavings(final Integer value) { + return CalendarEntityType.SAVINGS.value.equals(value); + } + + public static boolean isSavings(final String name) { + return CalendarEntityType.SAVINGS.name().equalsIgnoreCase(name); } public boolean isCenter() { - return this.value.equals(CalendarEntityType.CENTERS.getValue()); + return this == CENTERS; } public boolean isGroup() { - return this.value.equals(CalendarEntityType.GROUPS.getValue()); + return this == GROUPS; } public boolean isLoan() { - return this.value.equals(CalendarEntityType.LOANS.getValue()); + return this == LOANS; } public boolean isClient() { - return this.value.equals(CalendarEntityType.CLIENTS.getValue()); + return this == CLIENTS; + } + + public boolean isSavings() { + return this == SAVINGS; } private static final Map entityNameToEnumMap = new HashMap<>(); @@ -146,17 +158,4 @@ public boolean isClient() { public static CalendarEntityType getEntityType(String entityType) { return entityNameToEnumMap.get(entityType.toLowerCase()); } - - public static boolean isSavings(final Integer value) { - return CalendarEntityType.SAVINGS.value.equals(value) ? true : false; - } - - public static boolean isSavings(final String name) { - return CalendarEntityType.SAVINGS.name().equalsIgnoreCase(name) ? true : false; - } - - public boolean isSavings() { - return this.value.equals(CalendarEntityType.SAVINGS.getValue()); - } - } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistory.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistory.java index 7c40d8f2526..cd2f1fbfe55 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistory.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarHistory.java @@ -25,6 +25,7 @@ import jakarta.persistence.Table; import java.time.LocalDate; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; @Entity @Table(name = "m_calendar_history") @@ -95,26 +96,24 @@ public String getRecurrence() { return this.recurrence; } - public LocalDate getStartDateLocalDate() { + public LocalDate getStartDate() { return this.startDate; } - public LocalDate getEndDateLocalDate() { + public LocalDate getEndDate() { return this.endDate; } - public boolean isEndDateAfterOrEqual(final LocalDate compareDate) { - return this.endDate != null && compareDate != null - && (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)); + public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) { + return this.startDate != null && compareDate != null && !DateUtils.isAfter(this.startDate, compareDate); } - public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) { - return this.startDate != null && compareDate != null - && (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)); + public boolean isEndDateAfterOrEqual(final LocalDate compareDate) { + return this.endDate != null && compareDate != null && !DateUtils.isBefore(this.endDate, compareDate); } public boolean isBetweenStartAndEndDate(final LocalDate compareDate) { - return isStartDateBeforeOrEqual(compareDate) && (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)); + return isStartDateBeforeOrEqual(compareDate) && (getEndDate() == null || isEndDateAfterOrEqual(compareDate)); } public void updateEndDate(LocalDate historyCalEndDate) { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java index 486c2fe9af1..b06c1afaa15 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java @@ -103,14 +103,14 @@ private static LocalDateTime getNextRecurringDate(final Recur recur, final Local final DateTime periodStart = new DateTime(java.util.Date.from(startDate.atZone(ZoneId.systemDefault()).toInstant())); final Date seed = convertToiCal4JCompatibleDate(seedDate); final Date nextRecDate = recur.getNextDate(seed, periodStart); - return nextRecDate == null ? null : LocalDateTime.ofInstant(nextRecDate.toInstant(), DateUtils.getSystemZoneId()); + return nextRecDate == null ? null : LocalDateTime.ofInstant(nextRecDate.toInstant(), DateUtils.getDateTimeZoneOfTenant()); } private static LocalDate getNextRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate startDate) { final DateTime periodStart = new DateTime(java.util.Date.from(startDate.atStartOfDay(ZoneId.systemDefault()).toInstant())); final Date seed = convertToiCal4JCompatibleDate(seedDate.atStartOfDay()); final Date nextRecDate = recur.getNextDate(seed, periodStart); - return nextRecDate == null ? null : LocalDate.ofInstant(nextRecDate.toInstant(), DateUtils.getSystemZoneId()); + return nextRecDate == null ? null : LocalDate.ofInstant(nextRecDate.toInstant(), DateUtils.getDateTimeZoneOfTenant()); } private static Date convertToiCal4JCompatibleDate(final LocalDateTime inputDate) { @@ -125,16 +125,16 @@ private static Date convertToiCal4JCompatibleDate(final LocalDateTime inputDate) } public static Collection getRecurringDates(final String recurringRule, final LocalDate seedDate, final LocalDate endDate) { - final LocalDate periodStartDate = DateUtils.getLocalDateOfTenant(); - final LocalDate periodEndDate = (endDate == null) ? DateUtils.getLocalDateOfTenant().plusYears(5) : endDate; + final LocalDate periodEndDate = endDate == null ? periodStartDate.plusYears(5) : endDate; return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate); } public static Collection getRecurringDatesFrom(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) { - final LocalDate periodStartDate = (startDate == null) ? DateUtils.getLocalDateOfTenant() : startDate; - final LocalDate periodEndDate = DateUtils.getLocalDateOfTenant().plusYears(5); + LocalDate currentDate = DateUtils.getLocalDateOfTenant(); + final LocalDate periodStartDate = startDate == null ? currentDate : startDate; + final LocalDate periodEndDate = currentDate.plusYears(5); return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate); } @@ -150,7 +150,6 @@ public static Collection getRecurringDates(final String recurringRule public static Collection getRecurringDates(final String recurringRule, final LocalDate seedDate, final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount, boolean isSkippMeetingOnFirstDay, final Integer numberOfDays) { - final Recur recur = CalendarUtils.getICalRecur(recurringRule); return getRecurringDates(recur, seedDate, periodStartDate, periodEndDate, maxCount, isSkippMeetingOnFirstDay, numberOfDays); @@ -173,7 +172,6 @@ private static Collection getRecurringDates(final Recur recur, final private static Collection convertToLocalDateList(final DateList dates, final LocalDate seedDate, final PeriodFrequencyType frequencyType, boolean isSkippMeetingOnFirstDay, final Integer numberOfDays) { - final Collection recurringDates = new ArrayList<>(); for (@SuppressWarnings("rawtypes") @@ -719,15 +717,12 @@ public static LocalDate getNextScheduleDate(final Calendar calendar, final Local if (recur == null) { return null; } - LocalDate date = startDate; final LocalDate seedDate = calendar.getStartDateLocalDate(); /** * if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) { date = date.plusDays(1); } **/ - final LocalDate scheduleDate = getNextRecurringDate(recur, seedDate, date); - - return scheduleDate; + return getNextRecurringDate(recur, seedDate, startDate); } public static void validateNthDayOfMonthFrequency(DataValidatorBuilder baseDataValidator, final String repeatsOnNthDayOfMonthParamName, diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java index c499ecde6a6..f1a055199cf 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java @@ -100,10 +100,10 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "lastname", length = 50) private String lastname; - @Column(name = "fullname", length = 100) + @Column(name = "fullname", length = 160) private String fullname; - @Column(name = "display_name", length = 100, nullable = false) + @Column(name = "display_name", length = 160, nullable = false) private String displayName; @Column(name = "mobile_no", length = 50, unique = true) @@ -407,10 +407,6 @@ public boolean isPending() { return ClientStatus.fromInt(this.status).isPending(); } - private boolean isDateInTheFuture(final LocalDate localDate) { - return localDate.isAfter(DateUtils.getBusinessLocalDate()); - } - public boolean isRejected() { return ClientStatus.fromInt(this.status).isRejected(); } @@ -444,89 +440,69 @@ private void validateNameParts(final List dataValidationError } private void validateActivationDate(final List dataValidationErrors) { - - if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) { - + if (getSubmittedOnDate() != null && DateUtils.isDateInTheFuture(getSubmittedOnDate())) { final String defaultUserMessage = "submitted date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.in.the.future", defaultUserMessage, ClientApiConstants.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - - if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) { - + if (getActivationDate() != null && DateUtils.isAfter(getSubmittedOnDate(), getActivationDate())) { final String defaultUserMessage = "submitted date cannot be after the activation date"; final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.after.activation.date", defaultUserMessage, ClientApiConstants.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - - if (getReopenedDate() != null && getActivationLocalDate() != null && getReopenedDate().isAfter(getActivationLocalDate())) { - + if (getActivationDate() != null && DateUtils.isAfter(getReopenedDate(), getActivationDate())) { final String defaultUserMessage = "reopened date cannot be after the submittedon date"; final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.after.reopened.date", defaultUserMessage, ClientApiConstants.reopenedDateParamName, this.reopenedDate); dataValidationErrors.add(error); } - - if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) { - + if (DateUtils.isDateInTheFuture(getActivationDate())) { final String defaultUserMessage = "Activation date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.activationDate.in.the.future", - defaultUserMessage, ClientApiConstants.activationDateParamName, getActivationLocalDate()); + defaultUserMessage, ClientApiConstants.activationDateParamName, getActivationDate()); dataValidationErrors.add(error); } - - if (getActivationLocalDate() != null) { - if (this.office.isOpeningDateAfter(getActivationLocalDate())) { + if (getActivationDate() != null) { + if (this.office.isOpeningDateAfter(getActivationDate())) { final String defaultUserMessage = "Client activation date cannot be a date before the office opening date."; final ApiParameterError error = ApiParameterError.parameterError( "error.msg.clients.activationDate.cannot.be.before.office.activation.date", defaultUserMessage, - ClientApiConstants.activationDateParamName, getActivationLocalDate()); + ClientApiConstants.activationDateParamName, getActivationDate()); dataValidationErrors.add(error); } } } public void deriveDisplayName() { - - StringBuilder nameBuilder = new StringBuilder(); - Integer legalForm = this.getLegalForm(); - if (legalForm == null || LegalForm.fromInt(legalForm).isPerson()) { - if (StringUtils.isNotBlank(this.firstname)) { - nameBuilder.append(this.firstname).append(' '); - } - - if (StringUtils.isNotBlank(this.middlename)) { - nameBuilder.append(this.middlename).append(' '); - } - - if (StringUtils.isNotBlank(this.lastname)) { - nameBuilder.append(this.lastname); - } - - if (StringUtils.isNotBlank(this.fullname)) { - nameBuilder = new StringBuilder(this.fullname); - } - } else if (LegalForm.fromInt(legalForm).isEntity()) { - if (StringUtils.isNotBlank(this.fullname)) { - nameBuilder = new StringBuilder(this.fullname); + if (StringUtils.isNotBlank(this.fullname)) { + this.displayName = this.fullname; + } else { + StringBuilder nameBuilder = new StringBuilder(); + if (legalForm == null || LegalForm.fromInt(legalForm).isPerson()) { + if (StringUtils.isNotBlank(this.firstname)) { + nameBuilder.append(this.firstname); + } + if (StringUtils.isNotBlank(this.middlename)) { + if (!nameBuilder.isEmpty()) { + nameBuilder.append(' '); + } + nameBuilder.append(this.middlename); + } + if (StringUtils.isNotBlank(this.lastname)) { + if (!nameBuilder.isEmpty()) { + nameBuilder.append(' '); + } + nameBuilder.append(this.lastname); + } } + this.displayName = nameBuilder.toString(); } - - this.displayName = nameBuilder.toString(); - } - - public LocalDate getActivationLocalDate() { - return this.activationDate; - } - - public LocalDate getOfficeJoiningLocalDate() { - return this.officeJoiningDate; } public boolean isOfficeIdentifiedBy(final Long officeId) { @@ -621,7 +597,7 @@ public Long subStatusId() { } public boolean isActivatedAfter(final LocalDate submittedOn) { - return getActivationLocalDate().isAfter(submittedOn); + return DateUtils.isAfter(getActivationDate(), submittedOn); } public boolean isChildOfGroup(final Long groupId) { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateDTO.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateDTO.java index 31ded2d528d..5fd8b006632 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateDTO.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/floatingrates/data/FloatingRateDTO.java @@ -21,6 +21,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.util.Collection; +import org.apache.fineract.infrastructure.core.service.DateUtils; public class FloatingRateDTO { @@ -43,7 +44,7 @@ public BigDecimal fetchBaseRate(LocalDate date) { BigDecimal rate = null; for (FloatingRatePeriodData periodData : this.baseLendingRatePeriods) { final LocalDate periodFromDate = periodData.getFromDate(); - if (periodFromDate.isBefore(date) || periodFromDate.isEqual(date)) { + if (!DateUtils.isAfter(periodFromDate, date)) { rate = periodData.getInterestRate(); break; } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRate.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRate.java index 2393d2962e2..3091f451303 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRate.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/floatingrates/domain/FloatingRate.java @@ -89,9 +89,8 @@ public static FloatingRate createNew(JsonCommand command) { final String name = command.stringValueOfParameterNamed("name"); final boolean isBaseLendingRate = command.parameterExists("isBaseLendingRate") - ? command.booleanPrimitiveValueOfParameterNamed("isBaseLendingRate") - : false; - final boolean isActive = command.parameterExists("isActive") ? command.booleanPrimitiveValueOfParameterNamed("isActive") : true; + && command.booleanPrimitiveValueOfParameterNamed("isBaseLendingRate"); + final boolean isActive = !command.parameterExists("isActive") || command.booleanPrimitiveValueOfParameterNamed("isActive"); final List floatingRatePeriods = getRatePeriods(command); return new FloatingRate(name, isBaseLendingRate, isActive, floatingRatePeriods); @@ -109,8 +108,7 @@ private static List getRatePeriods(final JsonCommand command final LocalDate fromDate = helper.extractLocalDateNamed("fromDate", ratePeriod, new HashSet()); final BigDecimal interestRate = ratePeriodObject.get("interestRate").getAsBigDecimal(); final boolean isDifferentialToBaseLendingRate = helper.parameterExists("isDifferentialToBaseLendingRate", ratePeriod) - ? ratePeriodObject.get("isDifferentialToBaseLendingRate").getAsBoolean() - : false; + && ratePeriodObject.get("isDifferentialToBaseLendingRate").getAsBoolean(); final boolean isActive = true; ratePeriods.add(new FloatingRatePeriod(fromDate, interestRate, isDifferentialToBaseLendingRate, isActive)); } @@ -170,7 +168,7 @@ private void updateRatePeriods(final List newRatePeriods) { if (this.floatingRatePeriods != null) { for (FloatingRatePeriod ratePeriod : this.floatingRatePeriods) { LocalDate fromDate = ratePeriod.getFromDate(); - if (fromDate.isAfter(today)) { + if (DateUtils.isAfter(fromDate, today)) { ratePeriod.setActive(false); } } @@ -188,7 +186,7 @@ public Collection fetchInterestRates(final FloatingRateD for (FloatingRatePeriod floatingRatePeriod : this.floatingRatePeriods) { if (floatingRatePeriod.isActive()) { // will enter - if (applicableRates.isEmpty() && floatingRateDTO.getStartDate().isBefore(floatingRatePeriod.fetchFromDate())) { + if (applicableRates.isEmpty() && DateUtils.isBefore(floatingRateDTO.getStartDate(), floatingRatePeriod.fetchFromDate())) { if (floatingRateDTO.isFloatingInterestRate()) { addPeriodData = true; } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/group/data/GroupGeneralData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/group/data/GroupGeneralData.java index 6d12ced4eef..a99fd76f466 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/group/data/GroupGeneralData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/group/data/GroupGeneralData.java @@ -340,7 +340,7 @@ public String getHierarchy() { } public boolean isChildGroup() { - return this.centerId == null ? false : true; + return this.centerId != null; } public Long getParentId() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/group/domain/Group.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/group/domain/Group.java index 37a16d4f523..84d73c8e1a0 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/group/domain/Group.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/group/domain/Group.java @@ -259,7 +259,7 @@ private void setActivationDate(final LocalDate activationDate, final AppUser log } public boolean isActivatedAfter(final LocalDate submittedOn) { - return getActivationLocalDate().isAfter(submittedOn); + return DateUtils.isAfter(getActivationDate(), submittedOn); } public boolean isNotActive() { @@ -267,11 +267,7 @@ public boolean isNotActive() { } public boolean isActive() { - return this.status != null ? GroupingTypeStatus.fromInt(this.status).isActive() : false; - } - - private boolean isDateInTheFuture(final LocalDate localDate) { - return localDate.isAfter(DateUtils.getBusinessLocalDate()); + return this.status != null && GroupingTypeStatus.fromInt(this.status).isActive(); } public boolean isNotPending() { @@ -316,7 +312,7 @@ public Map update(final JsonCommand command) { final String dateFormatAsInput = command.dateFormat(); final String localeAsInput = command.locale(); - if (command.isChangeInLocalDateParameterNamed(GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate())) { + if (command.isChangeInLocalDateParameterNamed(GroupingTypesApiConstants.activationDateParamName, getActivationDate())) { final String valueAsInput = command.stringValueOfParameterNamed(GroupingTypesApiConstants.activationDateParamName); actualChanges.put(GroupingTypesApiConstants.activationDateParamName, valueAsInput); actualChanges.put(GroupingTypesApiConstants.dateFormatParamName, dateFormatAsInput); @@ -347,7 +343,7 @@ public LocalDate getSubmittedOnDate() { return this.submittedOnDate; } - public LocalDate getActivationLocalDate() { + public LocalDate getActivationDate() { return this.activationDate; } @@ -494,7 +490,7 @@ public boolean isChildClient(final Long clientId) { } public boolean isChildGroup() { - return this.parent == null ? false : true; + return this.parent != null; } @@ -503,18 +499,17 @@ public boolean isClosed() { } public void close(final AppUser currentUser, final CodeValue closureReason, final LocalDate closureDate) { - if (isClosed()) { final String errorMessage = "Group with identifier " + getId() + " is alread closed."; throw new InvalidGroupStateTransitionException(this.groupLevel.getLevelName(), "close", "already.closed", errorMessage, getId()); } - if (isNotPending() && getActivationLocalDate().isAfter(closureDate)) { + if (isNotPending() && DateUtils.isAfter(getActivationDate(), closureDate)) { final String errorMessage = "The Group closure Date " + closureDate + " cannot be before the group Activation Date " - + getActivationLocalDate() + "."; + + getActivationDate() + "."; throw new InvalidGroupStateTransitionException(this.groupLevel.getLevelName(), "close", - "date.cannot.before.group.actvation.date", errorMessage, closureDate, getActivationLocalDate()); + "date.cannot.before.group.actvation.date", errorMessage, closureDate, getActivationDate()); } this.closureReason = closureReason; @@ -644,9 +639,7 @@ public Set getActiveClientMembers() { } private void validateActivationDate(final List dataValidationErrors) { - - if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) { - + if (getSubmittedOnDate() != null && DateUtils.isDateInTheFuture(getSubmittedOnDate())) { final String defaultUserMessage = "Submitted on date cannot be in the future."; final String globalisationMessageCode = "error.msg.group.submittedOnDate.in.the.future"; final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, @@ -654,31 +647,26 @@ private void validateActivationDate(final List dataValidation dataValidationErrors.add(error); } - - if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) { - + if (getActivationDate() != null && DateUtils.isAfter(getSubmittedOnDate(), getActivationDate())) { final String defaultUserMessage = "Submitted on date cannot be after the activation date"; final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.submittedOnDate.after.activation.date", defaultUserMessage, GroupingTypesApiConstants.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - - if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) { - + if (getActivationDate() != null && DateUtils.isDateInTheFuture(getActivationDate())) { final String defaultUserMessage = "Activation date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.activationDate.in.the.future", - defaultUserMessage, GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate()); + defaultUserMessage, GroupingTypesApiConstants.activationDateParamName, getActivationDate()); dataValidationErrors.add(error); } - - if (getActivationLocalDate() != null) { - if (this.office.isOpeningDateAfter(getActivationLocalDate())) { + if (getActivationDate() != null) { + if (this.office.isOpeningDateAfter(getActivationDate())) { final String defaultUserMessage = "Activation date cannot be a date before the office opening date."; final ApiParameterError error = ApiParameterError.parameterError( "error.msg.group.activationDate.cannot.be.before.office.activation.date", defaultUserMessage, - GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate()); + GroupingTypesApiConstants.activationDateParamName, getActivationDate()); dataValidationErrors.add(error); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/group/domain/StaffAssignmentHistory.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/group/domain/StaffAssignmentHistory.java index 7e2874a1629..373bfe10310 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/group/domain/StaffAssignmentHistory.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/group/domain/StaffAssignmentHistory.java @@ -25,6 +25,7 @@ import jakarta.persistence.Table; import java.time.LocalDate; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.staff.domain.Staff; @Entity @@ -73,7 +74,7 @@ public void updateEndDate(final LocalDate endDate) { } public boolean matchesStartDateOf(final LocalDate matchingDate) { - return getStartDate().isEqual(matchingDate); + return DateUtils.isEqual(matchingDate, getStartDate()); } public LocalDate getStartDate() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java index 337167181e3..2a8b31de953 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java @@ -412,7 +412,7 @@ public boolean isChargeTransactionAndNotReversed() { } public boolean occursOn(final LocalDate occursOnDate) { - return getTransactionDate().isEqual(occursOnDate); + return DateUtils.isEqual(occursOnDate, getTransactionDate()); } public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, final LocalDateInterval boundedBy, @@ -426,7 +426,7 @@ public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, fi LocalDate balanceStartDate = getTransactionDate(); LocalDate balanceEndDate = getEndOfBalanceLocalDate(); - if (boundedBy.startDate().isAfter(balanceStartDate)) { + if (DateUtils.isAfter(boundedBy.startDate(), balanceStartDate)) { balanceStartDate = boundedBy.startDate(); final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); @@ -442,7 +442,7 @@ public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, fi } } - if (balanceEndDate.isAfter(boundedBy.endDate())) { + if (DateUtils.isAfter(balanceEndDate, boundedBy.endDate())) { balanceEndDate = boundedBy.endDate(); final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); @@ -489,7 +489,7 @@ public Set getSavingsAccountChargesPaid() { public void updateCumulativeBalanceAndDates(final MonetaryCurrency currency, final LocalDate endOfBalanceDate) { // balance end date should not be before transaction date - if (endOfBalanceDate != null && endOfBalanceDate.isBefore(this.transactionDate)) { + if (endOfBalanceDate != null && DateUtils.isBefore(endOfBalanceDate, this.transactionDate)) { this.balanceEndDate = this.transactionDate; } else if (endOfBalanceDate != null) { this.balanceEndDate = endOfBalanceDate; @@ -511,7 +511,7 @@ public boolean isFeeChargeAndNotReversed() { public boolean isFeeCharge() { final SavingsAccountChargesPaidByData chargePaidBy = getSavingsAccountChargePaidBy(); - return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false; + return isPayCharge() && chargePaidBy != null && chargePaidBy.isFeeCharge(); } public void setChargesPaidByData(final SavingsAccountChargesPaidByData savingsAccountChargesPaidByData) { @@ -528,7 +528,7 @@ public boolean isPenaltyChargeAndNotReversed() { public boolean isPenaltyCharge() { final SavingsAccountChargesPaidByData chargePaidBy = getSavingsAccountChargePaidBy(); - return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false; + return isPayCharge() && chargePaidBy != null && chargePaidBy.isPenaltyCharge(); } public boolean isWaiveFeeChargeAndNotReversed() { @@ -537,7 +537,7 @@ public boolean isWaiveFeeChargeAndNotReversed() { public boolean isWaiveFeeCharge() { final SavingsAccountChargesPaidByData chargePaidBy = getSavingsAccountChargePaidBy(); - return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false; + return isWaiveCharge() && chargePaidBy != null && chargePaidBy.isFeeCharge(); } public boolean isWaiveCharge() { @@ -550,7 +550,7 @@ public boolean isWaivePenaltyChargeAndNotReversed() { public boolean isWaivePenaltyCharge() { final SavingsAccountChargesPaidByData chargePaidBy = getSavingsAccountChargePaidBy(); - return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false; + return isWaiveCharge() && chargePaidBy != null && chargePaidBy.isPenaltyCharge(); } private SavingsAccountChargesPaidByData getSavingsAccountChargePaidBy() { @@ -582,7 +582,7 @@ public Map toMapData(final CurrencyData currencyData, final Long thisTransactionData.put("id", getId()); thisTransactionData.put("officeId", officeId); thisTransactionData.put("type", transactionType); - thisTransactionData.put("reversed", Boolean.valueOf(isReversed())); + thisTransactionData.put("reversed", isReversed()); thisTransactionData.put("date", getTransactionDate()); thisTransactionData.put("currency", currencyData); thisTransactionData.put("amount", this.amount); @@ -592,10 +592,8 @@ public Map toMapData(final CurrencyData currencyData, final Long thisTransactionData.put("paymentTypeId", this.paymentDetailData.getPaymentType().getId()); } - /*** - * Sending data in a map, though in savings we currently expect a transaction to always repay a single charge - * (or may repay a part of a single charge too) - ***/ + // Sending data in a map, though in savings we currently expect a transaction to always repay a single charge + // (or may repay a part of a single charge too) if (!this.chargesPaidByData.isEmpty()) { final List> savingsChargesPaidData = new ArrayList<>(); for (final SavingsAccountChargesPaidByData chargePaidBy : this.chargesPaidByData) { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java index 1fe4df28eb7..5e7a36e48ca 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.List; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; @@ -55,8 +56,7 @@ public List determineInterestPostingPeriods(final LocalDate s LocalDate periodEndDate = periodStartDate; LocalDate actualPeriodStartDate = periodStartDate; - while (!periodStartDate.isAfter(interestPostingUpToDate) && !periodEndDate.isAfter(interestPostingUpToDate)) { - + while (!DateUtils.isAfter(periodStartDate, interestPostingUpToDate) && !DateUtils.isAfter(periodEndDate, interestPostingUpToDate)) { final LocalDate interestPostingLocalDate = determineInterestPostingPeriodEndDateFrom(periodStartDate, postingPeriodType, interestPostingUpToDate, financialYearBeginningMonth); @@ -64,8 +64,7 @@ public List determineInterestPostingPeriods(final LocalDate s if (!postInterestAsOn.isEmpty()) { for (LocalDate transactiondate : postInterestAsOn) { - if (periodStartDate.isBefore(transactiondate) - && (periodEndDate.isAfter(transactiondate) || periodEndDate.isEqual(transactiondate))) { + if (DateUtils.isAfter(transactiondate, periodStartDate) && !DateUtils.isAfter(transactiondate, periodEndDate)) { periodEndDate = transactiondate.minusDays(1); actualPeriodStartDate = periodEndDate; break; @@ -75,7 +74,7 @@ public List determineInterestPostingPeriods(final LocalDate s postingPeriods.add(LocalDateInterval.create(periodStartDate, periodEndDate)); - if (actualPeriodStartDate.isEqual(periodEndDate)) { + if (DateUtils.isEqual(actualPeriodStartDate, periodEndDate)) { periodEndDate = actualPeriodStartDate.plusDays(1); periodStartDate = actualPeriodStartDate.plusDays(1); } else { @@ -128,7 +127,7 @@ private LocalDate determineInterestPostingPeriodEndDateFrom(final LocalDate peri break; case QUATERLY: for (LocalDate quarterlyDate : quarterlyDates) { - if (quarterlyDate.isAfter(periodStartDate)) { + if (DateUtils.isAfter(quarterlyDate, periodStartDate)) { periodEndDate = quarterlyDate; isEndDateSet = true; break; @@ -141,7 +140,7 @@ private LocalDate determineInterestPostingPeriodEndDateFrom(final LocalDate peri break; case BIANNUAL: for (LocalDate biannualDate : biannualDates) { - if (biannualDate.isAfter(periodStartDate)) { + if (DateUtils.isAfter(biannualDate, periodStartDate)) { periodEndDate = biannualDate; isEndDateSet = true; break; diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundInterestHelper.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundInterestHelper.java index 3d460fef175..4b918c0198a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundInterestHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/CompoundInterestHelper.java @@ -21,6 +21,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; @@ -59,7 +60,7 @@ public Money calculateInterestForAllPostingPeriods(final MonetaryCurrency curren // account and if already transfered then it includes in interest // calculation. if (!(postingPeriod.isInterestTransfered() || !interestTransferEnabled - || (lockUntil != null && !postingPeriod.dateOfPostingTransaction().isAfter(lockUntil)))) { + || (lockUntil != null && !DateUtils.isAfter(postingPeriod.dateOfPostingTransaction(), lockUntil)))) { compoundInterestValues.setcompoundedInterest(BigDecimal.ZERO); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/EndOfDayBalance.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/EndOfDayBalance.java index 58b51995f6d..ce821427c42 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/EndOfDayBalance.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/EndOfDayBalance.java @@ -22,6 +22,7 @@ import java.math.MathContext; import java.time.LocalDate; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; @@ -136,7 +137,6 @@ public BigDecimal calculateInterestOnBalanceAndInterest(final BigDecimal interes * @return */ public EndOfDayBalance upTo(final LocalDateInterval compoundingPeriodInterval, final LocalDate upToInterestCalculationDate) { - Money startingBalance = this.openingBalance; LocalDate balanceStartDate = this.date; @@ -144,7 +144,7 @@ public EndOfDayBalance upTo(final LocalDateInterval compoundingPeriodInterval, f int daysOfBalance = this.numberOfDays; - if (this.date.isBefore(compoundingPeriodInterval.startDate())) { + if (DateUtils.isBefore(this.date, compoundingPeriodInterval.startDate())) { balanceStartDate = compoundingPeriodInterval.startDate(); startingBalance = this.endOfDayBalance; final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, oldBalanceEndDate); @@ -152,12 +152,12 @@ public EndOfDayBalance upTo(final LocalDateInterval compoundingPeriodInterval, f } LocalDate balanceEndDate = balanceStartDate.plusDays(daysOfBalance - 1); - if (balanceEndDate.isAfter(compoundingPeriodInterval.endDate())) { + if (DateUtils.isAfter(balanceEndDate, compoundingPeriodInterval.endDate())) { balanceEndDate = compoundingPeriodInterval.endDate(); final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, balanceEndDate); daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate(); } - if (balanceEndDate.isAfter(upToInterestCalculationDate)) { + if (DateUtils.isAfter(balanceEndDate, upToInterestCalculationDate)) { balanceEndDate = upToInterestCalculationDate; final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, balanceEndDate); daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate(); @@ -167,7 +167,6 @@ public EndOfDayBalance upTo(final LocalDateInterval compoundingPeriodInterval, f } public boolean contains(final LocalDateInterval compoundingPeriodInterval) { - final LocalDate balanceUpToDate = this.date.plusDays(this.numberOfDays - 1); final LocalDateInterval balanceInterval = LocalDateInterval.create(this.date, balanceUpToDate); diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java index f65b7ce6020..f0bce2b1277 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java @@ -132,7 +132,7 @@ public static PostingPeriod createFrom(final LocalDateInterval periodInterval, f LocalDate balanceEndDate = periodInterval.endDate(); Integer numberOfDaysOfBalance = periodInterval.daysInPeriodInclusiveOfEndDate(); - if (balanceEndDate.isAfter(upToInterestCalculationDate)) { + if (DateUtils.isAfter(balanceEndDate, upToInterestCalculationDate)) { balanceEndDate = upToInterestCalculationDate; final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); @@ -206,7 +206,7 @@ public static PostingPeriod createFromDTO(final LocalDateInterval periodInterval LocalDate balanceEndDate = periodInterval.endDate(); Integer numberOfDaysOfBalance = periodInterval.daysInPeriodInclusiveOfEndDate(); - if (balanceEndDate.isAfter(upToInterestCalculationDate)) { + if (DateUtils.isAfter(balanceEndDate, upToInterestCalculationDate)) { balanceEndDate = upToInterestCalculationDate; final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); @@ -328,23 +328,21 @@ private static List compoundingPeriodsInPostingPeriod(final L compoundingPeriods.add(compoundingPeriod); break; case MONTHLY: - final LocalDate postingPeriodEndDate = postingPeriodInterval.endDate(); LocalDate periodStartDate = postingPeriodInterval.startDate(); LocalDate periodEndDate = periodStartDate; - while (!periodStartDate.isAfter(postingPeriodEndDate) && !periodEndDate.isAfter(postingPeriodEndDate)) { - + while (!DateUtils.isAfter(periodStartDate, postingPeriodEndDate) + && !DateUtils.isAfter(periodEndDate, postingPeriodEndDate)) { periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate, financialYearBeginningMonth); - if (periodEndDate.isAfter(postingPeriodEndDate)) { + if (DateUtils.isAfter(periodEndDate, postingPeriodEndDate)) { periodEndDate = postingPeriodEndDate; } final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate); if (postingPeriodInterval.contains(compoundingPeriodInterval)) { - compoundingPeriod = MonthlyCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances, upToInterestCalculationDate); compoundingPeriods.add(compoundingPeriod); @@ -364,17 +362,16 @@ private static List compoundingPeriodsInPostingPeriod(final L periodStartDate = postingPeriodInterval.startDate(); periodEndDate = periodStartDate; - while (!periodStartDate.isAfter(qPostingPeriodEndDate) && !periodEndDate.isAfter(qPostingPeriodEndDate)) { - + while (!DateUtils.isAfter(periodStartDate, qPostingPeriodEndDate) + && !DateUtils.isAfter(periodEndDate, qPostingPeriodEndDate)) { periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate, financialYearBeginningMonth); - if (periodEndDate.isAfter(qPostingPeriodEndDate)) { + if (DateUtils.isAfter(periodEndDate, qPostingPeriodEndDate)) { periodEndDate = qPostingPeriodEndDate; } final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate); if (postingPeriodInterval.contains(compoundingPeriodInterval)) { - compoundingPeriod = QuarterlyCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances, upToInterestCalculationDate); compoundingPeriods.add(compoundingPeriod); @@ -390,11 +387,11 @@ private static List compoundingPeriodsInPostingPeriod(final L periodStartDate = postingPeriodInterval.startDate(); periodEndDate = periodStartDate; - while (!periodStartDate.isAfter(bPostingPeriodEndDate) && !periodEndDate.isAfter(bPostingPeriodEndDate)) { - + while (!DateUtils.isAfter(periodStartDate, bPostingPeriodEndDate) + && !DateUtils.isAfter(periodEndDate, bPostingPeriodEndDate)) { periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate, financialYearBeginningMonth); - if (periodEndDate.isAfter(bPostingPeriodEndDate)) { + if (DateUtils.isAfter(periodEndDate, bPostingPeriodEndDate)) { periodEndDate = bPostingPeriodEndDate; } @@ -416,11 +413,12 @@ private static List compoundingPeriodsInPostingPeriod(final L periodStartDate = postingPeriodInterval.startDate(); periodEndDate = periodStartDate; - while (!periodStartDate.isAfter(aPostingPeriodEndDate) && !periodEndDate.isAfter(aPostingPeriodEndDate)) { + while (!DateUtils.isAfter(periodStartDate, aPostingPeriodEndDate) + && !DateUtils.isAfter(periodEndDate, aPostingPeriodEndDate)) { periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate, financialYearBeginningMonth); - if (periodEndDate.isAfter(aPostingPeriodEndDate)) { + if (DateUtils.isAfter(periodEndDate, aPostingPeriodEndDate)) { periodEndDate = aPostingPeriodEndDate; } @@ -483,7 +481,7 @@ private static LocalDate determineInterestPeriodEndDateFrom(final LocalDate peri case ANNUAL: periodEndDate = periodStartDate.withMonth(previousMonth); periodEndDate = periodEndDate.with(TemporalAdjusters.lastDayOfMonth()); - if (periodEndDate.isBefore(periodStartDate)) { + if (DateUtils.isBefore(periodEndDate, periodStartDate)) { periodEndDate = periodEndDate.plusYears(1); } break; diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/SavingsAccountTransactionDetailsForPostingPeriod.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/SavingsAccountTransactionDetailsForPostingPeriod.java index ce9a5465051..3a3145b377f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/SavingsAccountTransactionDetailsForPostingPeriod.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/SavingsAccountTransactionDetailsForPostingPeriod.java @@ -23,6 +23,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; @@ -54,7 +55,7 @@ public boolean spansAnyPortionOf(final LocalDateInterval periodInterval) { } public boolean occursOn(final LocalDate occursOnDate) { - return getTransactionDate().isEqual(occursOnDate); + return DateUtils.isEqual(occursOnDate, getTransactionDate()); } public EndOfDayBalance toEndOfDayBalance(final Money openingBalance) { @@ -92,7 +93,6 @@ public EndOfDayBalance toEndOfDayBalance(final LocalDateInterval periodInterval, } public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, final LocalDateInterval boundedBy) { - final MonetaryCurrency currency = openingBalance.getCurrency(); Money endOfDayBalance = openingBalance.copy(); @@ -101,7 +101,7 @@ public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, fi LocalDate balanceStartDate = getTransactionDate(); LocalDate balanceEndDate = getEndOfBalanceDate(); - if (boundedBy.startDate().isAfter(balanceStartDate)) { + if (DateUtils.isAfter(boundedBy.startDate(), balanceStartDate)) { balanceStartDate = boundedBy.startDate(); final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); @@ -121,7 +121,7 @@ public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, fi } } - if (balanceEndDate.isAfter(boundedBy.endDate())) { + if (DateUtils.isAfter(balanceEndDate, boundedBy.endDate())) { balanceEndDate = boundedBy.endDate(); final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentData.java index 708edb1877e..b2ea0c9f140 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentData.java @@ -27,6 +27,7 @@ import lombok.Getter; import org.apache.fineract.accounting.glaccount.data.GLAccountData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.service.DateUtils; @Getter public final class TaxComponentData implements Serializable { @@ -136,7 +137,7 @@ public BigDecimal getApplicablePercentage(final LocalDate date) { } private boolean occursOnDayFrom(final LocalDate target) { - return target != null && target.isAfter(startDate()); + return DateUtils.isAfter(target, startDate()); } public LocalDate startDate() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentHistoryData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentHistoryData.java index 086f65d4037..95adff63647 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentHistoryData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentHistoryData.java @@ -21,6 +21,7 @@ import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; +import org.apache.fineract.infrastructure.core.service.DateUtils; public class TaxComponentHistoryData implements Serializable { @@ -38,10 +39,7 @@ public TaxComponentHistoryData(final BigDecimal percentage, final LocalDate star } public boolean occursOnDayFromAndUpToAndIncluding(final LocalDate target) { - if (this.endDate == null) { - return target != null && target.isAfter(startDate()); - } - return target != null && target.isAfter(startDate()) && !target.isAfter(endDate()); + return DateUtils.isAfter(target, startDate()) && (endDate == null || !DateUtils.isAfter(target, endDate())); } public LocalDate startDate() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupMappingsData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupMappingsData.java index f0009cd8fd8..bc15a66703d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupMappingsData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupMappingsData.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.time.LocalDate; +import org.apache.fineract.infrastructure.core.service.DateUtils; public class TaxGroupMappingsData implements Serializable { @@ -44,10 +45,7 @@ public TaxComponentData getTaxComponent() { } public boolean occursOnDayFromAndUpToAndIncluding(final LocalDate target) { - if (this.endDate == null) { - return target != null && target.isAfter(startDate()); - } - return target != null && target.isAfter(startDate()) && !target.isAfter(endDate()); + return DateUtils.isAfter(target, startDate()) && (endDate == null || !DateUtils.isAfter(target, endDate())); } public LocalDate startDate() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponent.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponent.java index dd12b367237..ac0d0a0ef42 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponent.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponent.java @@ -167,7 +167,7 @@ public BigDecimal getApplicablePercentage(final LocalDate date) { } private boolean occursOnDayFrom(final LocalDate target) { - return target != null && target.isAfter(startDate()); + return DateUtils.isAfter(target, startDate()); } public Set getTaxComponentHistories() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentHistory.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentHistory.java index 63dfb54ea41..5330fa14c8e 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentHistory.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentHistory.java @@ -24,6 +24,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; @Entity @Table(name = "m_tax_component_history") @@ -62,10 +63,7 @@ public LocalDate endDate() { } public boolean occursOnDayFromAndUpToAndIncluding(final LocalDate target) { - if (this.endDate == null) { - return target != null && target.isAfter(startDate()); - } - return target != null && target.isAfter(startDate()) && !target.isAfter(endDate()); + return DateUtils.isAfter(target, startDate()) && (endDate == null || !DateUtils.isAfter(target, endDate())); } public BigDecimal getPercentage() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupMappings.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupMappings.java index e1d4ff49bad..ff95a1abd83 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupMappings.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupMappings.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.tax.api.TaxApiConstants; @Entity @@ -81,10 +82,7 @@ public void update(final LocalDate endDate, final List> chan } public boolean occursOnDayFromAndUpToAndIncluding(final LocalDate target) { - if (this.endDate == null) { - return target != null && target.isAfter(startDate()); - } - return target != null && target.isAfter(startDate()) && !target.isAfter(endDate()); + return DateUtils.isAfter(target, startDate()) && (endDate == null || !DateUtils.isAfter(target, endDate())); } public TaxComponent getTaxComponent() { diff --git a/fineract-core/src/main/java/org/apache/fineract/useradministration/service/AppUserConstants.java b/fineract-core/src/main/java/org/apache/fineract/useradministration/service/AppUserConstants.java index 0bb0ccae72b..25fbeb2658e 100644 --- a/fineract-core/src/main/java/org/apache/fineract/useradministration/service/AppUserConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/useradministration/service/AppUserConstants.java @@ -30,4 +30,6 @@ private AppUserConstants() { // TODO: Remove hard coding of system user name and make this a configurable parameter public static final String SYSTEM_USER_NAME = "system"; + public static final Long ADMIN_USER_ID = 1L; + public static final Long SYSTEM_USER_ID = 2L; } diff --git a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java index 5ab233ee772..91e4095a16c 100644 --- a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java +++ b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java @@ -32,7 +32,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.ActionContext; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; -import org.apache.fineract.infrastructure.core.exceptionmapper.IdempotentCommandProcessFailedExceptionMapper; +import org.apache.fineract.infrastructure.core.exceptionmapper.IdempotentCommandExceptionMapper; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -50,11 +50,11 @@ public void setUp() { @Test public void testInconsistentStatus() { - IdempotentCommandProcessFailedExceptionMapper mapper = new IdempotentCommandProcessFailedExceptionMapper(); + IdempotentCommandExceptionMapper mapper = new IdempotentCommandExceptionMapper(); CommandWrapper command = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); CommandSource source = CommandSource.fullEntryFrom(command, JsonCommand.from("{}"), null, "dummy-key", null); - IdempotentCommandProcessFailedException exception = new IdempotentCommandProcessFailedException(command, source); + IdempotentCommandProcessFailedException exception = new IdempotentCommandProcessFailedException(command, null, source); Response result = mapper.toResponse(exception); assertEquals(500, result.getStatus()); assertEquals("true", result.getHeaderString(IDEMPOTENT_CACHE_HEADER)); diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/accounting/journalentry/service/InvestorAccountingHelper.java b/fineract-investor/src/main/java/org/apache/fineract/investor/accounting/journalentry/service/InvestorAccountingHelper.java index 5ab1dd26715..944e4744be6 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/accounting/journalentry/service/InvestorAccountingHelper.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/accounting/journalentry/service/InvestorAccountingHelper.java @@ -37,6 +37,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping; import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.office.domain.Office; import org.springframework.stereotype.Service; @@ -61,7 +62,7 @@ public void checkForBranchClosures(Long officeId, final LocalDate transactionDat **/ GLClosure gLClosure = getLatestClosureByBranch(officeId); if (gLClosure != null) { - if (gLClosure.getClosingDate().isAfter(transactionDate) || gLClosure.getClosingDate().isEqual(transactionDate)) { + if (!DateUtils.isAfter(transactionDate, gLClosure.getClosingDate())) { throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.ACCOUNTING_CLOSED, gLClosure.getClosingDate(), null, null); } diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java index a9a4d24d933..c4790b920e5 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java @@ -170,7 +170,7 @@ private ExternalAssetOwnerTransfer fetchAndValidateEffectiveTransferForBuyback(f throw new ExternalAssetOwnerInitiateTransferException( String.format("This loan cannot be bought back, effective transfer is not in right state: %s", effectiveTransfers.get(0).getStatus())); - } else if (settlementDate.isBefore(effectiveTransfers.get(0).getSettlementDate())) { + } else if (DateUtils.isBefore(settlementDate, effectiveTransfers.get(0).getSettlementDate())) { throw new ExternalAssetOwnerInitiateTransferException( String.format("This loan cannot be bought back, settlement date is earlier than effective transfer settlement date: %s", effectiveTransfers.get(0).getSettlementDate())); @@ -251,7 +251,7 @@ private void validateSettlementDate(ExternalAssetOwnerTransfer externalAssetOwne } private void validateSettlementDate(LocalDate settlementDate) { - if (settlementDate.isBefore(ThreadLocalContextUtil.getBusinessDate())) { + if (DateUtils.isBeforeBusinessDate(settlementDate)) { throw new ExternalAssetOwnerInitiateTransferException("Settlement date cannot be in the past"); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java index 8e66b973e8f..243426ddd13 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java @@ -22,6 +22,7 @@ import java.time.LocalDate; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.DateUtils; /** * Immutable data object representing disbursement information. @@ -85,8 +86,7 @@ public int compareTo(final DisbursementData obj) { if (obj == null) { return -1; } - - return obj.expectedDisbursementDate.compareTo(this.expectedDisbursementDate); + return DateUtils.compare(obj.expectedDisbursementDate, this.expectedDisbursementDate); } public boolean isDueForDisbursement(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) { @@ -96,14 +96,11 @@ public boolean isDueForDisbursement(final LocalDate fromNotInclusive, final Loca private boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive, final LocalDate target) { - return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive); + return DateUtils.isAfter(target, fromNotInclusive) && !DateUtils.isAfter(target, upToAndInclusive); } public BigDecimal getWaivedChargeAmount() { - if (this.waivedChargeAmount == null) { - return BigDecimal.ZERO; - } - return this.waivedChargeAmount; + return this.waivedChargeAmount == null ? BigDecimal.ZERO : this.waivedChargeAmount; } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java index d1b64b81e4f..7b9858f07ca 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java @@ -22,6 +22,7 @@ import java.time.LocalDate; import lombok.Getter; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; @Getter @@ -64,7 +65,7 @@ public boolean isApplicable(final LocalDate fromDate, final LocalDate dueDate) { } private boolean occursOnDayFromAndUpTo(final LocalDate fromNotInclusive, final LocalDate upToInclusive, final LocalDate target) { - return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToInclusive); + return DateUtils.isAfter(target, fromNotInclusive) && !DateUtils.isAfter(target, upToInclusive); } public boolean isApplicable(final LocalDate fromDate) { @@ -72,7 +73,7 @@ public boolean isApplicable(final LocalDate fromDate) { } private boolean occursBefore(final LocalDate date, final LocalDate target) { - return target != null && !target.isAfter(date); + return target != null && !DateUtils.isAfter(target, date); } public boolean isSpecificToInstallment() { @@ -84,7 +85,7 @@ public boolean isIsSpecificToInstallment() { } public Boolean isProcessed() { - return this.isProcessed == null ? false : this.isProcessed; + return this.isProcessed != null && this.isProcessed; } public void setProcessed(Boolean isProcessed) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java index faf57def415..8a93201544a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.ListIterator; +import org.apache.fineract.infrastructure.core.service.DateUtils; public class LoanTermVariationsDataWrapper { @@ -54,7 +55,7 @@ private boolean hasNext(final LocalDate date, ListIterator installments = getRepaymentScheduleInstallments(); for (LoanRepaymentScheduleInstallment installment : installments) { if ((includeRecalculatedInterestComponent || !installment.isRecalculatedInterestComponent()) - && installment.getDueDate().isAfter(lastRepaymentDate)) { + && DateUtils.isAfter(installment.getDueDate(), lastRepaymentDate)) { lastRepaymentDate = installment.getDueDate(); } } @@ -1079,16 +1083,17 @@ public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLi } LocalDate transactionDate = getDisbursementDate(); + LocalDate businessDate = DateUtils.getBusinessLocalDate(); if (loanCharge.isDueDateCharge()) { - if (loanCharge.getDueLocalDate().isAfter(DateUtils.getBusinessLocalDate())) { - transactionDate = DateUtils.getBusinessLocalDate(); + if (DateUtils.isAfter(loanCharge.getDueLocalDate(), businessDate)) { + transactionDate = businessDate; } else { transactionDate = loanCharge.getDueLocalDate(); } } else if (loanCharge.isInstalmentFee()) { LocalDate repaymentDueDate = loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getRepaymentInstallment().getDueDate(); - if (repaymentDueDate.isAfter(DateUtils.getBusinessLocalDate())) { - transactionDate = DateUtils.getBusinessLocalDate(); + if (DateUtils.isAfter(repaymentDueDate, businessDate)) { + transactionDate = businessDate; } else { transactionDate = repaymentDueDate; } @@ -1108,21 +1113,21 @@ public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLi waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); addLoanTransaction(waiveLoanChargeTransaction); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() - && (loanCharge.getDueLocalDate() == null || DateUtils.getBusinessLocalDate().isAfter(loanCharge.getDueLocalDate()))) { + && DateUtils.isBefore(loanCharge.getDueLocalDate(), businessDate)) { regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); } - // Waive of charges whose due date falls after latest 'repayment' - // transaction dont require entire loan schedule to be reprocessed. + // Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule + // to be reprocessed. final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategyCode); if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { - /**** + /* * TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan * charges only waives the remaining amount. * * Consider removing this block of code or logically completing it for the future by getting the list of * affected Transactions - ***/ + */ final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); @@ -1284,7 +1289,6 @@ public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) { updateLoanScheduleDependentDerivedFields(); updateLoanSummaryDerivedFields(); applyAccurals(); - } public void updateLoanSchedule(final Collection installments) { @@ -1343,20 +1347,15 @@ private void applyPeriodicAccruals(final Collection accruals) { LocalDate transactionDateForRange = isBasedOnSubmittedOnDate ? loanTransaction.getLoanChargesPaid().stream().findFirst().get().getLoanCharge().getDueDate() : loanTransaction.getTransactionDate(); - boolean isInRange = installment.isFirstPeriod() - ? !transactionDateForRange.isBefore(installment.getFromDate()) - && !transactionDateForRange.isAfter(installment.getDueDate()) - : transactionDateForRange.isAfter(installment.getFromDate()) - && !transactionDateForRange.isAfter(installment.getDueDate()); - if (isInRange) { + if (installment.isInPeriod(transactionDateForRange)) { interest = interest.plus(loanTransaction.getInterestPortion(getCurrency())); fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency())); penality = penality.plus(loanTransaction.getPenaltyChargesPortion(getCurrency())); if (installment.getFeeChargesCharged(getCurrency()).isLessThan(fee) || installment.getInterestCharged(getCurrency()).isLessThan(interest) || installment.getPenaltyChargesCharged(getCurrency()).isLessThan(penality) - || (isInterestBearing() && getAccruedTill().isEqual(loanTransaction.getTransactionDate()) - && !installment.getDueDate().isEqual(getAccruedTill()))) { + || (isInterestBearing() && DateUtils.isEqual(getAccruedTill(), loanTransaction.getTransactionDate()) + && !DateUtils.isEqual(getAccruedTill(), installment.getDueDate()))) { interest = interest.minus(loanTransaction.getInterestPortion(getCurrency())); fee = fee.minus(loanTransaction.getFeeChargesPortion(getCurrency())); penality = penality.minus(loanTransaction.getPenaltyChargesPortion(getCurrency())); @@ -1369,14 +1368,13 @@ private void applyPeriodicAccruals(final Collection accruals) { } LoanRepaymentScheduleInstallment lastInstallment = getLastLoanRepaymentScheduleInstallment(); for (LoanTransaction loanTransaction : accruals) { - if (loanTransaction.getTransactionDate().isAfter(lastInstallment.getDueDate()) && !loanTransaction.isReversed()) { + if (!loanTransaction.isReversed() && DateUtils.isAfter(loanTransaction.getTransactionDate(), lastInstallment.getDueDate())) { loanTransaction.reverse(); } } } private void updateAccrualsForNonPeriodicAccruals(final Collection accruals) { - final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); ExternalId externalId = ExternalId.empty(); boolean isExternalIdAutoGenerationEnabled = TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled(); @@ -1601,24 +1599,22 @@ public Map loanApplicationModification(final JsonCommand command // the comparison should be done with the tenant date // (DateUtils.getBusinessDate()) and not the server date (new // LocalDate()) - if (getSubmittedOnDate().isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(getSubmittedOnDate())) { final String errorMessage = "The date on which a loan is submitted cannot be in the future."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, getSubmittedOnDate()); } - if (this.client != null) { - if (getSubmittedOnDate().isBefore(this.client.getActivationLocalDate())) { - final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date."; - throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, - getSubmittedOnDate()); - } - } else if (this.group != null && getSubmittedOnDate().isBefore(this.group.getActivationLocalDate())) { + if (this.client != null && DateUtils.isBefore(getSubmittedOnDate(), this.client.getActivationDate())) { + final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date."; + throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, + getSubmittedOnDate()); + } else if (this.group != null && DateUtils.isBefore(getSubmittedOnDate(), this.group.getActivationDate())) { final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, getSubmittedOnDate()); } - if (getSubmittedOnDate().isAfter(getExpectedDisbursedOnLocalDate())) { + if (DateUtils.isAfter(getSubmittedOnDate(), getExpectedDisbursedOnLocalDate())) { final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: " + getExpectedDisbursedOnLocalDate().toString(); throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage, @@ -1802,7 +1798,7 @@ private BigDecimal calculateOverdueAmountPercentageAppliedTo(final LoanCharge lo LoanRepaymentScheduleInstallment installment = loanCharge.getOverdueInstallmentCharge().getInstallment(); LocalDate graceDate = DateUtils.getBusinessLocalDate().minusDays(penaltyWaitPeriod); Money amount = Money.zero(getCurrency()); - if (graceDate.isAfter(installment.getDueDate())) { + if (DateUtils.isAfter(graceDate, installment.getDueDate())) { amount = calculateOverdueAmountPercentageAppliedTo(installment, loanCharge.getChargeCalculation()); if (!amount.isGreaterThanZero()) { loanCharge.setActive(false); @@ -2096,7 +2092,6 @@ public void loanApplicationSubmittal(final LoanScheduleModel loanSchedule, final final LoanLifecycleStateMachine lifecycleStateMachine, final LocalDate submittedOn, final ExternalId externalId, final boolean allowTransactionsOnHoliday, final List holidays, final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { - updateLoanSchedule(loanSchedule); lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, this); @@ -2111,7 +2106,7 @@ public void loanApplicationSubmittal(final LoanScheduleModel loanSchedule, final updateLoanScheduleDependentDerivedFields(); - if (submittedOn.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(submittedOn)) { final String errorMessage = "The date on which a loan is submitted cannot be in the future."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn, DateUtils.getBusinessLocalDate()); @@ -2120,7 +2115,7 @@ public void loanApplicationSubmittal(final LoanScheduleModel loanSchedule, final if (this.client != null && this.client.isActivatedAfter(submittedOn)) { final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, submittedOn, - client.getActivationLocalDate()); + client.getActivationDate()); } validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CREATED, submittedOn); @@ -2128,10 +2123,10 @@ public void loanApplicationSubmittal(final LoanScheduleModel loanSchedule, final if (this.group != null && this.group.isActivatedAfter(submittedOn)) { final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, submittedOn, - group.getActivationLocalDate()); + group.getActivationDate()); } - if (submittedOn.isAfter(getExpectedDisbursedOnLocalDate())) { + if (DateUtils.isAfter(submittedOn, getExpectedDisbursedOnLocalDate())) { final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: " + getExpectedDisbursedOnLocalDate().toString(); throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage, @@ -2150,11 +2145,8 @@ public void loanApplicationSubmittal(final LoanScheduleModel loanSchedule, final validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay); validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); - /** - * Copy interest recalculation settings if interest recalculation is enabled - */ + // Copy interest recalculation settings if interest recalculation is enabled if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { - this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails .createFrom(this.loanProduct.getProductInterestRecalculationDetails()); this.loanInterestRecalculationDetails.updateLoan(this); @@ -2204,7 +2196,7 @@ public Map loanApplicationRejection(final AppUser currentUser, f actualChanges.put(REJECTED_ON_DATE, rejectedOn.format(fmt)); actualChanges.put(CLOSED_ON_DATE, rejectedOn.format(fmt)); - if (rejectedOn.isBefore(getSubmittedOnDate())) { + if (DateUtils.isBefore(rejectedOn, getSubmittedOnDate())) { final String errorMessage = "The date on which a loan is rejected cannot be before its submittal date: " + getSubmittedOnDate().toString(); throw new InvalidLoanStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, rejectedOn, @@ -2213,7 +2205,7 @@ public Map loanApplicationRejection(final AppUser currentUser, f validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REJECTED, rejectedOn); - if (rejectedOn.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(rejectedOn)) { final String errorMessage = "The date on which a loan is rejected cannot be in the future."; throw new InvalidLoanStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, rejectedOn); } @@ -2253,7 +2245,7 @@ public Map loanApplicationWithdrawnByApplicant(final AppUser cur actualChanges.put(WITHDRAWN_ON_DATE, withdrawnOn.format(fmt)); actualChanges.put(CLOSED_ON_DATE, withdrawnOn.format(fmt)); - if (withdrawnOn.isBefore(getSubmittedOnDate())) { + if (DateUtils.isBefore(withdrawnOn, getSubmittedOnDate())) { final String errorMessage = "The date on which a loan is withdrawn cannot be before its submittal date: " + getSubmittedOnDate().toString(); throw new InvalidLoanStateTransitionException("withdraw", "cannot.be.before.submittal.date", errorMessage, command, @@ -2262,7 +2254,7 @@ public Map loanApplicationWithdrawnByApplicant(final AppUser cur validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_WITHDRAWN, withdrawnOn); - if (withdrawnOn.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(withdrawnOn)) { final String errorMessage = "The date on which a loan is withdrawn cannot be in the future."; throw new InvalidLoanStateTransitionException("withdraw", "cannot.be.a.future.date", errorMessage, command); } @@ -2358,9 +2350,8 @@ public Map loanApplicationApproval(final AppUser currentUser, fi actualChanges.put(APPROVED_ON_DATE, approvedOnDateChange); final LocalDate submittalDate = this.submittedOnDate; - if (approvedOn.isBefore(submittalDate)) { - final String errorMessage = "The date on which a loan is approved cannot be before its submittal date: " - + submittalDate.toString(); + if (DateUtils.isBefore(approvedOn, submittalDate)) { + final String errorMessage = "The date on which a loan is approved cannot be before its submittal date: " + submittalDate; throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, getApprovedOnDate(), submittalDate); } @@ -2369,7 +2360,7 @@ public Map loanApplicationApproval(final AppUser currentUser, fi this.expectedDisbursementDate = expecteddisbursementDate; actualChanges.put(EXPECTED_DISBURSEMENT_DATE, expectedDisbursementDate); - if (expecteddisbursementDate.isBefore(approvedOn)) { + if (DateUtils.isBefore(expecteddisbursementDate, approvedOn)) { final String errorMessage = "The expected disbursement date should be either on or after the approval date: " + approvedOn.toString(); throw new InvalidLoanStateTransitionException("expecteddisbursal", "should.be.on.or.after.approval.date", errorMessage, @@ -2379,7 +2370,7 @@ public Map loanApplicationApproval(final AppUser currentUser, fi validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_APPROVED, approvedOn); - if (approvedOn.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(approvedOn)) { final String errorMessage = "The date on which a loan is approved cannot be in the future."; throw new InvalidLoanStateTransitionException("approval", "cannot.be.a.future.date", errorMessage, getApprovedOnDate()); } @@ -2536,12 +2527,8 @@ private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolea boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean downPaymentEnabled) { LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate(); - if ((interestRecalculationEnabledParam - && (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam))) { + if ((interestRecalculationEnabledParam && (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) { regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); - } else if (downPaymentEnabled) { - LoanScheduleDTO loanSchedule = getRecalculatedSchedule(scheduleGeneratorDTO); - updateLoanSchedule(loanSchedule.getInstallments()); } } @@ -2606,7 +2593,7 @@ public boolean canDisburse(final LocalDate actualDisbursementDate) { LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus); if ((actualLoanStatus.isActive() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid()) && isAllTranchesNotDisbursed()) { - if (actualDisbursementDate.isBefore(loanSubmittedOnDate)) { + if (DateUtils.isBefore(actualDisbursementDate, loanSubmittedOnDate)) { final String errorMsg = "Loan can't be disbursed before " + loanSubmittedOnDate; throw new LoanDisbursalException(errorMsg, "actualdisbursementdate.before.submittedDate", loanSubmittedOnDate, actualDisbursementDate); @@ -2616,12 +2603,10 @@ && isAllTranchesNotDisbursed()) { return !statusEnum.hasStateOf(actualLoanStatus) || isMultiTrancheDisburse; } - public Money adjustDisburseAmount(final JsonCommand command, final LocalDate actualDisbursementDate) { + public Money adjustDisburseAmount(@NotNull JsonCommand command, @NotNull LocalDate actualDisbursementDate) { Money disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal().zero(); BigDecimal principalDisbursed = command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalDisbursedParameterName); - if (this.actualDisbursementDate == null) { - this.actualDisbursementDate = actualDisbursementDate; - } else if (actualDisbursementDate.isBefore(this.actualDisbursementDate)) { + if (this.actualDisbursementDate == null || DateUtils.isBefore(actualDisbursementDate, this.actualDisbursementDate)) { this.actualDisbursementDate = actualDisbursementDate; } BigDecimal diff = BigDecimal.ZERO; @@ -2721,13 +2706,14 @@ private Collection fetchUndisbursedDetail() { LocalDate date = null; for (LoanDisbursementDetails disbursementDetail : getDisbursementDetails()) { if (disbursementDetail.actualDisbursementDate() == null) { - if (date == null || disbursementDetail.expectedDisbursementDate().compareTo(date) == 0 ? Boolean.TRUE : Boolean.FALSE) { + LocalDate expectedDate = disbursementDetail.expectedDisbursementDate(); + if (date == null || DateUtils.isEqual(expectedDate, date)) { disbursementDetails.add(disbursementDetail); - date = disbursementDetail.expectedDisbursementDate(); - } else if (disbursementDetail.expectedDisbursementDate().isBefore(date)) { + date = expectedDate; + } else if (DateUtils.isBefore(expectedDate, date)) { disbursementDetails.clear(); disbursementDetails.add(disbursementDetail); - date = disbursementDetail.expectedDisbursementDate(); + date = expectedDate; } } } @@ -2739,9 +2725,9 @@ private LoanDisbursementDetails fetchLastDisburseDetail() { LocalDate date = this.actualDisbursementDate; if (date != null) { for (LoanDisbursementDetails disbursementDetail : getDisbursementDetails()) { - if (disbursementDetail.actualDisbursementDate() != null && (disbursementDetail.actualDisbursementDate().isAfter(date) - || disbursementDetail.actualDisbursementDate().compareTo(date) == 0 ? Boolean.TRUE : Boolean.FALSE)) { - date = disbursementDetail.actualDisbursementDate(); + LocalDate actualDate = disbursementDetail.actualDisbursementDate(); + if (!DateUtils.isBefore(actualDate, date)) { + date = actualDate; details = disbursementDetail; } } @@ -2753,7 +2739,7 @@ private boolean isDisbursementMissed() { boolean isDisbursementMissed = false; for (LoanDisbursementDetails disbursementDetail : getDisbursementDetails()) { if (disbursementDetail.actualDisbursementDate() == null - && DateUtils.getBusinessLocalDate().isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) { + && DateUtils.isBeforeBusinessDate(disbursementDetail.expectedDisbursementDateAsLocalDate())) { isDisbursementMissed = true; break; } @@ -2837,11 +2823,11 @@ public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO sche final MathContext mc = new MathContext(8, roundingMode); final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO); - LoanScheduleGenerator loanScheduleGenerator = null; + LoanScheduleGenerator loanScheduleGenerator; if (loanApplicationTerms.isEqualAmortization()) { if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) { final LoanScheduleGenerator decliningLoanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory() - .create(InterestMethod.DECLINING_BALANCE); + .create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE); Set loanCharges = getActiveCharges(); LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, scheduleGeneratorDTO.getHolidayDetailDTO()); @@ -2850,9 +2836,11 @@ public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO sche .updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged())); } - loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(InterestMethod.FLAT); + loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(), + InterestMethod.FLAT); } else { - loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getInterestMethod()); + loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(), + loanApplicationTerms.getInterestMethod()); } return loanScheduleGenerator.generate(mc, loanApplicationTerms, getActiveCharges(), scheduleGeneratorDTO.getHolidayDetailDTO()); @@ -2871,7 +2859,7 @@ private BigDecimal constructFloatingInterestRates(final BigDecimal annualNominal LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( LoanEnumerations.loanVariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), periodData.getInterestRate(), dateValue, isSpecificToInstallment); - if (!interestRateStartDate.isBefore(periodData.getFromDateAsLocalDate())) { + if (!DateUtils.isBefore(interestRateStartDate, periodData.getFromDateAsLocalDate())) { interestRateStartDate = periodData.getFromDateAsLocalDate(); interestRate = periodData.getInterestRate(); } @@ -2932,26 +2920,25 @@ private void handleDisbursementTransaction(final LocalDate disbursedOn, final Pa updateLoanOutstandingBalances(); } - if (getApprovedOnDate() != null && disbursedOn.isBefore(getApprovedOnDate())) { + if (getApprovedOnDate() != null && DateUtils.isBefore(disbursedOn, getApprovedOnDate())) { final String errorMessage = "The date on which a loan is disbursed cannot be before its approval date: " + getApprovedOnDate().toString(); throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.before.approval.date", errorMessage, disbursedOn, getApprovedOnDate()); } - if (getExpectedFirstRepaymentOnDate() != null - && (disbursedOn.isAfter(this.fetchRepaymentScheduleInstallment(1).getDueDate()) - || disbursedOn.isAfter(getExpectedFirstRepaymentOnDate())) - && disbursedOn.compareTo(this.actualDisbursementDate) == 0 ? Boolean.TRUE : Boolean.FALSE) { + LocalDate expectedDate = getExpectedFirstRepaymentOnDate(); + if (expectedDate != null && (DateUtils.isAfter(disbursedOn, this.fetchRepaymentScheduleInstallment(1).getDueDate()) + || DateUtils.isAfter(disbursedOn, expectedDate)) && DateUtils.isEqual(disbursedOn, this.actualDisbursementDate)) { final String errorMessage = "submittedOnDate cannot be after the loans expectedFirstRepaymentOnDate: " - + getExpectedFirstRepaymentOnDate().toString(); + + expectedDate.toString(); throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.after.expected.first.repayment.date", errorMessage, - disbursedOn, getExpectedFirstRepaymentOnDate()); + disbursedOn, expectedDate); } validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSED, disbursedOn); - if (disbursedOn.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(disbursedOn)) { final String errorMessage = "The date on which a loan with identifier : " + this.accountNumber + " is disbursed cannot be in the future."; throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.a.future.date", errorMessage, disbursedOn); @@ -2959,34 +2946,31 @@ private void handleDisbursementTransaction(final LocalDate disbursedOn, final Pa } - public void handleDownPayment(final BigDecimal disbursedAmount, final JsonCommand command, + public LoanTransaction handleDownPayment(final BigDecimal disbursedAmount, final JsonCommand command, final ScheduleGeneratorDTO scheduleGeneratorDTO) { - if (isAutoRepaymentForDownPaymentEnabled()) { - LocalDate disbursedOn = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE); - BigDecimal disbursedAmountPercentageForDownPayment = this.loanRepaymentScheduleDetail - .getDisbursedAmountPercentageForDownPayment(); - ExternalId externalId = ExternalId.empty(); - if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) { - externalId = ExternalId.generate(); - } - Money downPaymentMoney = Money.of(getCurrency(), - MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 19)); - LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), downPaymentMoney, null, disbursedOn, - externalId); - - LoanEvent event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER; - validateRepaymentTypeAccountStatus(downPaymentTransaction, event); - HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); - validateRepaymentDateIsOnHoliday(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(), - holidayDetailDTO.getHolidays()); - validateRepaymentDateIsOnNonWorkingDay(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(), - holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); - - handleRepaymentOrRecoveryOrWaiverTransaction(downPaymentTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO); + LocalDate disbursedOn = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE); + BigDecimal disbursedAmountPercentageForDownPayment = this.loanRepaymentScheduleDetail.getDisbursedAmountPercentageForDownPayment(); + ExternalId externalId = ExternalId.empty(); + if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) { + externalId = ExternalId.generate(); } + Money downPaymentMoney = Money.of(getCurrency(), + MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 19)); + LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), downPaymentMoney, null, disbursedOn, externalId); + + LoanEvent event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER; + validateRepaymentTypeAccountStatus(downPaymentTransaction, event); + HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); + validateRepaymentDateIsOnHoliday(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(), + holidayDetailDTO.getHolidays()); + validateRepaymentDateIsOnNonWorkingDay(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(), + holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); + + handleRepaymentOrRecoveryOrWaiverTransaction(downPaymentTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO); + return downPaymentTransaction; } - private boolean isAutoRepaymentForDownPaymentEnabled() { + public boolean isAutoRepaymentForDownPaymentEnabled() { return this.loanRepaymentScheduleDetail.isEnableDownPayment() && this.loanRepaymentScheduleDetail.isEnableAutoRepaymentForDownPayment(); } @@ -3037,9 +3021,8 @@ public Map undoDisbursal(final ScheduleGeneratorDTO scheduleGene final boolean isScheduleRegenerateRequired = isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate); this.actualDisbursementDate = null; this.disbursedBy = null; - boolean isDisbursedAmountChanged = approvedPrincipal.compareTo(this.loanRepaymentScheduleDetail.getPrincipal().getAmount()) == 0 - ? Boolean.FALSE - : Boolean.TRUE; + boolean isDisbursedAmountChanged = !MathUtil.isEqualTo(approvedPrincipal, + this.loanRepaymentScheduleDetail.getPrincipal().getAmount()); this.loanRepaymentScheduleDetail.setPrincipal(this.approvedPrincipal); // Remove All the Disbursement Details If the Loan Product is disabled and exists one if (this.loanProduct().isDisallowExpectedDisbursements() && !getDisbursementDetails().isEmpty()) { @@ -3203,7 +3186,7 @@ public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachi validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); - if (paymentTransaction.getTransactionDate().isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(paymentTransaction.getTransactionDate())) { final String errorMessage = "The date on which a loan charge paid cannot be in the future."; throw new InvalidLoanStateTransitionException("charge.payment", "cannot.be.a.future.date", errorMessage, paymentTransaction.getTransactionDate()); @@ -3284,14 +3267,14 @@ private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(fi } final LocalDate loanTransactionDate = loanTransaction.getTransactionDate(); - if (loanTransactionDate.isBefore(getDisbursementDate())) { + if (DateUtils.isBefore(loanTransactionDate, getDisbursementDate())) { final String errorMessage = "The transaction date cannot be before the loan disbursement date: " + getDisbursementDate().toString(); throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage, loanTransactionDate, getDisbursementDate()); } - if (loanTransactionDate.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(loanTransactionDate)) { final String errorMessage = "The transaction date cannot be in the future."; throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate); } @@ -3326,7 +3309,7 @@ private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(fi boolean reprocess = true; if (!isForeclosure() && isTransactionChronologicallyLatest && adjustedTransaction == null - && loanTransaction.getTransactionDate().isEqual(DateUtils.getBusinessLocalDate()) && currentInstallment != null + && DateUtils.isEqualBusinessDate(loanTransaction.getTransactionDate()) && currentInstallment != null && currentInstallment.getTotalOutstanding(getCurrency()).isEqualTo(loanTransaction.getAmount(getCurrency()))) { reprocess = false; } @@ -3408,8 +3391,7 @@ private List retrieveListOfIncomePostingTransactions() { incomePostTransactions.add(transaction); } } - final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); - Collections.sort(incomePostTransactions, transactionComparator); + incomePostTransactions.sort(LoanTransactionComparator.INSTANCE); return incomePostTransactions; } @@ -3417,27 +3399,23 @@ private List retrieveListOfTransactionsPostDisbursement() { final List repaymentsOrWaivers = new ArrayList<>(); List trans = getLoanTransactions(); for (final LoanTransaction transaction : trans) { - if (transaction.isNotReversed() - && (transaction.isChargeOff() || !(transaction.isDisbursement() || transaction.isNonMonetaryTransaction()))) { + if (transaction.isNotReversed() && (transaction.isChargeOff() || !transaction.isNonMonetaryTransaction())) { repaymentsOrWaivers.add(transaction); } } - final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); - Collections.sort(repaymentsOrWaivers, transactionComparator); + repaymentsOrWaivers.sort(LoanTransactionComparator.INSTANCE); return repaymentsOrWaivers; } public List retrieveListOfTransactionsPostDisbursementExcludeAccruals() { final List repaymentsOrWaivers = new ArrayList<>(); for (final LoanTransaction transaction : this.loanTransactions) { - if (transaction.isNotReversed() - && !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement() - || transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) { + if (transaction.isNotReversed() && !(transaction.isAccrual() || transaction.isRepaymentAtDisbursement() + || transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) { repaymentsOrWaivers.add(transaction); } } - final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); - Collections.sort(repaymentsOrWaivers, transactionComparator); + repaymentsOrWaivers.sort(LoanTransactionComparator.INSTANCE); return repaymentsOrWaivers; } @@ -3448,8 +3426,7 @@ private List retrieveListOfTransactionsExcludeAccruals() { repaymentsOrWaivers.add(transaction); } } - final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); - Collections.sort(repaymentsOrWaivers, transactionComparator); + repaymentsOrWaivers.sort(LoanTransactionComparator.INSTANCE); return repaymentsOrWaivers; } @@ -3460,8 +3437,7 @@ private List retrieveListOfAccrualTransactions() { transactions.add(transaction); } } - final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); - Collections.sort(transactions, transactionComparator); + transactions.sort(LoanTransactionComparator.INSTANCE); return transactions; } @@ -3472,8 +3448,7 @@ public List retrieveListOfTransactionsByType(final LoanTransact transactions.add(transaction); } } - final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); - Collections.sort(transactions, transactionComparator); + transactions.sort(LoanTransactionComparator.INSTANCE); return transactions; } @@ -3605,30 +3580,26 @@ private void handleLoanOverpayment(LocalDate transactionDate, final LoanLifecycl private boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction, final List loanTransactions) { - boolean isChronologicallyLatestRepaymentOrWaiver = true; final LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); for (final LoanTransaction previousTransaction : loanTransactions) { if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed() - && (currentTransactionDate.isBefore(previousTransaction.getTransactionDate()) - || currentTransactionDate.isEqual(previousTransaction.getTransactionDate()))) { + && !DateUtils.isAfter(currentTransactionDate, previousTransaction.getTransactionDate())) { isChronologicallyLatestRepaymentOrWaiver = false; break; } } - return isChronologicallyLatestRepaymentOrWaiver; } private boolean isAfterLatRepayment(final LoanTransaction loanTransaction, final List loanTransactions) { - boolean isAfterLatRepayment = true; final LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); for (final LoanTransaction previousTransaction : loanTransactions) { if (previousTransaction.isRepaymentLikeType() && previousTransaction.isNotReversed() - && currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) { + && DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())) { isAfterLatRepayment = false; break; } @@ -3638,18 +3609,16 @@ private boolean isAfterLatRepayment(final LoanTransaction loanTransaction, final private boolean isChronologicallyLatestTransaction(final LoanTransaction loanTransaction, final List loanTransactions) { - boolean isChronologicallyLatestRepaymentOrWaiver = true; final LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); for (final LoanTransaction previousTransaction : loanTransactions) { - if (previousTransaction.isNotReversed() && (currentTransactionDate.isBefore(previousTransaction.getTransactionDate()) - || currentTransactionDate.isEqual(previousTransaction.getTransactionDate()))) { + if (previousTransaction.isNotReversed() + && !DateUtils.isAfter(currentTransactionDate, previousTransaction.getTransactionDate())) { isChronologicallyLatestRepaymentOrWaiver = false; break; } } - return isChronologicallyLatestRepaymentOrWaiver; } @@ -3671,7 +3640,7 @@ public LocalDate possibleNextRepaymentDate() { } LocalDate possibleNextRepaymentDate = earliestUnpaidInstallmentDate; - if (lastTransactionDate != null && lastTransactionDate.isAfter(earliestUnpaidInstallmentDate)) { + if (DateUtils.isAfter(lastTransactionDate, earliestUnpaidInstallmentDate)) { possibleNextRepaymentDate = lastTransactionDate; } @@ -3884,7 +3853,7 @@ public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, fin changes.put(WRITTEN_OFF_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); changes.put("externalId", externalId); - if (writtenOffOnLocalDate.isBefore(getDisbursementDate())) { + if (DateUtils.isBefore(writtenOffOnLocalDate, getDisbursementDate())) { final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: " + getDisbursementDate().toString(); throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.before.submittal.date", errorMessage, @@ -3893,14 +3862,14 @@ public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, fin validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.WRITE_OFF_OUTSTANDING, writtenOffOnLocalDate); - if (writtenOffOnLocalDate.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(writtenOffOnLocalDate)) { final String errorMessage = "The date on which a loan is written off cannot be in the future."; throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate); } loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, externalId); LocalDate lastTransactionDate = getLastUserTransactionDate(); - if (lastTransactionDate.isAfter(writtenOffOnLocalDate)) { + if (DateUtils.isAfter(lastTransactionDate, writtenOffOnLocalDate)) { final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions."; throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage, writtenOffOnLocalDate); @@ -3946,8 +3915,8 @@ private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO s public LoanTransaction getLatestTransaction() { LoanTransaction transaction = null; for (LoanTransaction loanTransaction : this.loanTransactions) { - if (!loanTransaction.isReversed() - && (transaction == null || transaction.getTransactionDate().isBefore(loanTransaction.getTransactionDate()))) { + if (!loanTransaction.isReversed() && (transaction == null + || DateUtils.isBefore(transaction.getTransactionDate(), loanTransaction.getTransactionDate()))) { transaction = loanTransaction; } } @@ -3975,14 +3944,14 @@ public ChangedTransactionDetail close(final JsonCommand command, final LoanLifec changes.put(CLOSED_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.REPAID_IN_FULL, closureDate); - if (closureDate.isBefore(getDisbursementDate())) { + if (DateUtils.isBefore(closureDate, getDisbursementDate())) { final String errorMessage = "The date on which a loan is closed cannot be before the loan disbursement date: " + getDisbursementDate().toString(); throw new InvalidLoanStateTransitionException("close", "cannot.be.before.submittal.date", errorMessage, closureDate, getDisbursementDate()); } - if (closureDate.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(closureDate)) { final String errorMessage = "The date on which a loan is closed cannot be in the future."; throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate); } @@ -4067,14 +4036,14 @@ public void closeAsMarkedForReschedule(final JsonCommand command, final LoanLife changes.put(CLOSED_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); changes.put("rescheduledOnDate", command.stringValueOfParameterNamed(TRANSACTION_DATE)); - if (this.rescheduledOnDate.isBefore(getDisbursementDate())) { + if (DateUtils.isBefore(this.rescheduledOnDate, getDisbursementDate())) { final String errorMessage = "The date on which a loan is rescheduled cannot be before the loan disbursement date: " + getDisbursementDate().toString(); throw new InvalidLoanStateTransitionException("close.reschedule", "cannot.be.before.submittal.date", errorMessage, this.rescheduledOnDate, getDisbursementDate()); } - if (this.rescheduledOnDate.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(this.rescheduledOnDate)) { final String errorMessage = "The date on which a loan is rescheduled cannot be in the future."; throw new InvalidLoanStateTransitionException("close.reschedule", "cannot.be.a.future.date", errorMessage, this.rescheduledOnDate); @@ -4169,7 +4138,7 @@ private boolean hasDisbursementTransaction() { } public boolean isSubmittedOnDateAfter(final LocalDate compareDate) { - return this.submittedOnDate != null && this.submittedOnDate.isAfter(compareDate); + return DateUtils.isAfter(this.submittedOnDate, compareDate); } public LocalDate getSubmittedOnDate() { @@ -4235,12 +4204,11 @@ private boolean isActualDisbursedOnDateEarlierOrLaterThanExpected(final LocalDat boolean isRegenerationRequired = false; if (this.loanProduct.isMultiDisburseLoan()) { LoanDisbursementDetails details = fetchLastDisburseDetail(); - if (details != null && details.expectedDisbursementDate().compareTo(details.actualDisbursementDate()) == 0 ? Boolean.FALSE - : Boolean.TRUE) { + if (details != null && !DateUtils.isEqual(details.expectedDisbursementDate(), details.actualDisbursementDate())) { isRegenerationRequired = true; } } - return !this.expectedDisbursementDate.isEqual(actualDisbursedOnDate) || isRegenerationRequired; + return isRegenerationRequired || !DateUtils.isEqual(actualDisbursedOnDate, this.expectedDisbursementDate); } private boolean isRepaymentScheduleRegenerationRequiredForDisbursement(final LocalDate actualDisbursementDate) { @@ -4337,39 +4305,29 @@ public MonetaryCurrency getCurrency() { } public void reassignLoanOfficer(final Staff newLoanOfficer, final LocalDate assignmentDate) { - final LoanOfficerAssignmentHistory latestHistoryRecord = findLatestIncompleteHistoryRecord(); final LoanOfficerAssignmentHistory lastAssignmentRecord = findLastAssignmentHistoryRecord(newLoanOfficer); // assignment date should not be less than loan submitted date if (isSubmittedOnDateAfter(assignmentDate)) { - final String errorMessage = "The Loan Officer assignment date (" + assignmentDate.toString() + ") cannot be before loan submitted date (" + getSubmittedOnDate().toString() + ")."; - throw new LoanOfficerAssignmentDateException("cannot.be.before.loan.submittal.date", errorMessage, assignmentDate, getSubmittedOnDate()); - } else if (lastAssignmentRecord != null && lastAssignmentRecord.isEndDateAfter(assignmentDate)) { - final String errorMessage = "The Loan Officer assignment date (" + assignmentDate + ") cannot be before previous Loan Officer unassigned date (" + lastAssignmentRecord.getEndDate() + ")."; - throw new LoanOfficerAssignmentDateException("cannot.be.before.previous.unassignement.date", errorMessage, assignmentDate, lastAssignmentRecord.getEndDate()); - - } else if (DateUtils.getBusinessLocalDate().isBefore(assignmentDate)) { - + } else if (DateUtils.isDateInTheFuture(assignmentDate)) { final String errorMessage = "The Loan Officer assignment date (" + assignmentDate + ") cannot be in the future."; - throw new LoanOfficerAssignmentDateException("cannot.be.a.future.date", errorMessage, assignmentDate); - } else if (latestHistoryRecord != null && this.loanOfficer.identifiedBy(newLoanOfficer)) { latestHistoryRecord.updateStartDate(assignmentDate); } else if (latestHistoryRecord != null && latestHistoryRecord.matchesStartDateOf(assignmentDate)) { latestHistoryRecord.updateLoanOfficer(newLoanOfficer); this.loanOfficer = newLoanOfficer; - } else if (latestHistoryRecord != null && latestHistoryRecord.hasStartDateBefore(assignmentDate)) { + } else if (latestHistoryRecord != null && latestHistoryRecord.isBeforeStartDate(assignmentDate)) { final String errorMessage = "Loan with identifier " + getId() + " was already assigned before date " + assignmentDate; throw new LoanOfficerAssignmentDateException("is.before.last.assignment.date", errorMessage, getId(), assignmentDate); } else { @@ -4401,27 +4359,18 @@ public void removeLoanOfficer(final LocalDate unassignDate) { } private void validateUnassignDate(final LoanOfficerAssignmentHistory latestHistoryRecord, final LocalDate unassignDate) { - - final LocalDate today = DateUtils.getBusinessLocalDate(); - - if (latestHistoryRecord.getStartDate().isAfter(unassignDate)) { - + if (DateUtils.isAfter(latestHistoryRecord.getStartDate(), unassignDate)) { final String errorMessage = "The Loan officer Unassign date(" + unassignDate + ") cannot be before its assignment date (" + latestHistoryRecord.getStartDate() + ")."; - throw new LoanOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, getId(), getLoanOfficer().getId(), latestHistoryRecord.getStartDate(), unassignDate); - - } else if (unassignDate.isAfter(today)) { - + } else if (DateUtils.isDateInTheFuture(unassignDate)) { final String errorMessage = "The Loan Officer Unassign date (" + unassignDate + ") cannot be in the future."; - throw new LoanOfficerUnassignmentDateException("cannot.be.a.future.date", errorMessage, unassignDate); } } private LoanOfficerAssignmentHistory findLatestIncompleteHistoryRecord() { - LoanOfficerAssignmentHistory latestRecordWithNoEndDate = null; for (final LoanOfficerAssignmentHistory historyRecord : this.loanOfficerHistory) { if (historyRecord.isCurrentRecord()) { @@ -4433,10 +4382,8 @@ private LoanOfficerAssignmentHistory findLatestIncompleteHistoryRecord() { } private LoanOfficerAssignmentHistory findLastAssignmentHistoryRecord(final Staff newLoanOfficer) { - LoanOfficerAssignmentHistory lastAssignmentRecordLatestEndDate = null; for (final LoanOfficerAssignmentHistory historyRecord : this.loanOfficerHistory) { - if (historyRecord.isCurrentRecord() && !historyRecord.isSameLoanOfficer(newLoanOfficer)) { lastAssignmentRecordLatestEndDate = historyRecord; break; @@ -4564,14 +4511,14 @@ private void classifyTransactionsBasedOnChargeOffDate(List> List existingReversedTransactionIds, String currencyCode) { // Before filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff, currencyCode, existingTransactionIds, - existingReversedTransactionIds, transaction -> transaction.getTransactionDate().isBefore(getChargedOffOnDate())); + existingReversedTransactionIds, transaction -> DateUtils.isBefore(transaction.getTransactionDate(), getChargedOffOnDate())); // On filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, currencyCode, existingTransactionIds, existingReversedTransactionIds, - transaction -> transaction.getTransactionDate().isEqual(getChargedOffOnDate())); + transaction -> DateUtils.isEqual(transaction.getTransactionDate(), getChargedOffOnDate())); // After filterTransactionsByChargeOffDate(newLoanTransactionsAfterChargeOff, currencyCode, existingTransactionIds, - existingReversedTransactionIds, transaction -> transaction.getTransactionDate().isAfter(getChargedOffOnDate())); + existingReversedTransactionIds, transaction -> DateUtils.isAfter(transaction.getTransactionDate(), getChargedOffOnDate())); } private Map getAccountingBridgeDataGenericAttributes(final String currencyCode, boolean isAccountTransfer) { @@ -4671,7 +4618,7 @@ public Money getReceivableInterest(final LocalDate tillDate) { Money receivableInterest = Money.zero(getCurrency()); for (final LoanTransaction transaction : this.loanTransactions) { if (transaction.isNotReversed() && !transaction.isRepaymentAtDisbursement() && !transaction.isDisbursement() - && !transaction.getTransactionDate().isAfter(tillDate)) { + && !DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) { if (transaction.isAccrual()) { receivableInterest = receivableInterest.plus(transaction.getInterestPortion(getCurrency())); } else if (transaction.isRepaymentLikeType() || transaction.isInterestWaiver()) { @@ -4709,11 +4656,8 @@ public LocalDate getClosedOnDate() { public void updateLoanRepaymentScheduleDates(final String recurringRule, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final LocalDate presentMeetingDate, final LocalDate newMeetingDate, final boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) { - // first repayment's from date is same as disbursement date. - /* - * meetingStartDate is used as seedDate Capture the seedDate from user and use the seedDate as meetingStart date - */ + // meetingStartDate is used as seedDate Capture the seedDate from user and use the seedDate as meetingStart date LocalDate tmpFromDate = getDisbursementDate(); final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType(); @@ -4725,16 +4669,11 @@ public void updateLoanRepaymentScheduleDates(final String recurringRule, final b LocalDate latestRepaymentDate = null; List installments = getRepaymentScheduleInstallments(); for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { - LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); - - if (oldDueDate.isEqual(presentMeetingDate) || oldDueDate.isAfter(presentMeetingDate)) { - + if (!DateUtils.isBefore(oldDueDate, presentMeetingDate)) { if (isFirstTime) { - isFirstTime = false; newRepaymentDate = newMeetingDate; - } else { // tmpFromDate.plusDays(1) is done to make sure // getNewRepaymentMeetingDate method returns next meeting @@ -4742,11 +4681,10 @@ public void updateLoanRepaymentScheduleDates(final String recurringRule, final b newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recurringRule, tmpFromDate, tmpFromDate.plusDays(1), loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays); } - if (isHolidayEnabled) { newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays); } - if (latestRepaymentDate == null || latestRepaymentDate.isBefore(newRepaymentDate)) { + if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) { latestRepaymentDate = newRepaymentDate; } loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate); @@ -4770,7 +4708,6 @@ public void updateLoanRepaymentScheduleDates(final String recurringRule, final b public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final boolean isSkipRepaymentonfirstdayofmonth, final Integer numberofDays) { - // first repayment's from date is same as disbursement date. LocalDate tmpFromDate = getDisbursementDate(); final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType(); @@ -4782,20 +4719,18 @@ public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, f LocalDate latestRepaymentDate = null; List installments = getRepaymentScheduleInstallments(); for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { - LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); // FIXME: AA this won't update repayment dates before current date. - if (oldDueDate.isAfter(seedDate) && oldDueDate.isAfter(DateUtils.getBusinessLocalDate())) { - + if (DateUtils.isAfter(oldDueDate, seedDate) && DateUtils.isDateInTheFuture(oldDueDate)) { newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, seedDate, oldDueDate, loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays); final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType, loanRepaymentInterval, tmpFromDate); - if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) { + if (DateUtils.isAfter(newRepaymentDate, maxDateLimitForNewRepayment)) { newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate, loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays); } @@ -4803,7 +4738,7 @@ public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, f if (isHolidayEnabled) { newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays); } - if (latestRepaymentDate == null || latestRepaymentDate.isBefore(newRepaymentDate)) { + if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) { latestRepaymentDate = newRepaymentDate; } @@ -4952,9 +4887,9 @@ public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final Working } private void validateActivityNotBeforeClientOrGroupTransferDate(final LoanEvent event, final LocalDate activityDate) { - if (this.client != null && this.client.getOfficeJoiningLocalDate() != null) { - final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate(); - if (activityDate.isBefore(clientOfficeJoiningDate)) { + if (this.client != null && this.client.getOfficeJoiningDate() != null) { + final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate(); + if (DateUtils.isBefore(activityDate, clientOfficeJoiningDate)) { String errorMessage = null; String action = null; String postfix = null; @@ -5037,7 +4972,7 @@ private void validateActivityNotBeforeLastTransactionDate(final LoanEvent event, return; } LocalDate lastTransactionDate = getLastUserTransactionDate(); - if (lastTransactionDate.isAfter(activityDate)) { + if (DateUtils.isAfter(lastTransactionDate, activityDate)) { String errorMessage = null; String action = null; String postfix = null; @@ -5068,7 +5003,7 @@ public void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final LoanTra final String reversedOrCreated) { if (repaymentTransaction.isRepaymentLikeType() && !repaymentTransaction.isChargeRefund()) { for (LoanTransaction txn : this.getLoanTransactions()) { - if (txn.isChargeRefund() && repaymentTransaction.getTransactionDate().isBefore(txn.getTransactionDate())) { + if (txn.isChargeRefund() && DateUtils.isBefore(repaymentTransaction.getTransactionDate(), txn.getTransactionDate())) { final String errorMessage = "loan.transaction.cant.be." + reversedOrCreated + ".because.later.charge.refund.exists"; final String details = "Loan Transaction: " + this.getId() + " Can't be " + reversedOrCreated + " because a Later Charge Refund Exists."; @@ -5082,7 +5017,7 @@ public LocalDate getLastUserTransactionDate() { LocalDate currentTransactionDate = getDisbursementDate(); for (final LoanTransaction previousTransaction : this.loanTransactions) { if (!(previousTransaction.isReversed() || previousTransaction.isAccrual() || previousTransaction.isIncomePosting()) - && currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) { + && DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())) { currentTransactionDate = previousTransaction.getTransactionDate(); } } @@ -5092,7 +5027,8 @@ public LocalDate getLastUserTransactionDate() { public LocalDate getLastRepaymentDate() { LocalDate currentTransactionDate = getDisbursementDate(); for (final LoanTransaction previousTransaction : this.loanTransactions) { - if (previousTransaction.isRepaymentLikeType() && currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) { + if (previousTransaction.isRepaymentLikeType() + && DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())) { currentTransactionDate = previousTransaction.getTransactionDate(); } } @@ -5438,7 +5374,8 @@ public BigDecimal retriveLastEmiAmount() { BigDecimal emiAmount = this.fixedEmiAmount; LocalDate startDate = this.getDisbursementDate(); for (LoanTermVariations loanTermVariations : this.loanTermVariations) { - if (loanTermVariations.getTermType().isEMIAmountVariation() && !startDate.isAfter(loanTermVariations.getTermApplicableFrom())) { + if (loanTermVariations.getTermType().isEMIAmountVariation() + && !DateUtils.isAfter(startDate, loanTermVariations.getTermApplicableFrom())) { startDate = loanTermVariations.getTermApplicableFrom(); emiAmount = loanTermVariations.getTermValue(); } @@ -5586,9 +5523,9 @@ public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleG for (final LoanCharge loanCharge : charges) { if (!loanCharge.isDueAtDisbursement()) { updateOverdueScheduleInstallment(loanCharge); - if (loanCharge.getDueLocalDate() == null || !lastRepaymentDate.isBefore(loanCharge.getDueLocalDate())) { - if ((loanCharge.isInstalmentFee() || !loanCharge.isWaived()) - && (loanCharge.getDueLocalDate() == null || !lastTransactionDate.isAfter(loanCharge.getDueLocalDate()))) { + if (loanCharge.getDueLocalDate() == null || !DateUtils.isBefore(lastRepaymentDate, loanCharge.getDueLocalDate())) { + if ((loanCharge.isInstalmentFee() || !loanCharge.isWaived()) && (loanCharge.getDueLocalDate() == null + || !DateUtils.isAfter(lastTransactionDate, loanCharge.getDueLocalDate()))) { recalculateLoanCharge(loanCharge, generatorDTO.getPenaltyWaitPeriod()); loanCharge.updateWaivedAmount(getCurrency()); } @@ -5634,7 +5571,7 @@ public void processIncomeTransactions() { List incomeTransactions = retrieveListOfIncomePostingTransactions(); List accrualTransactions = retrieveListOfAccrualTransactions(); for (LoanInterestRecalcualtionAdditionalDetails compoundingDetail : compoundingDetails) { - if (!compoundingDetail.getEffectiveDate().isBefore(DateUtils.getBusinessLocalDate())) { + if (!DateUtils.isBeforeBusinessDate(compoundingDetail.getEffectiveDate())) { break; } LoanTransaction incomeTransaction = getTransactionForDate(incomeTransactions, compoundingDetail.getEffectiveDate()); @@ -5651,7 +5588,7 @@ public void processIncomeTransactions() { private void reverseTransactionsOnOrAfter(List transactions, LocalDate date) { for (LoanTransaction loanTransaction : transactions) { - if (!loanTransaction.getTransactionDate().isBefore(date)) { + if (!DateUtils.isBefore(loanTransaction.getTransactionDate(), date)) { loanTransaction.reverse(); } } @@ -5724,8 +5661,8 @@ private void determineFeeDetails(LocalDate fromDate, LocalDate toDate, HashMap installments = new ArrayList<>(); List repaymentSchedule = getRepaymentScheduleInstallments(); for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : repaymentSchedule) { - if (loanRepaymentScheduleInstallment.getDueDate().isAfter(fromDate) - && !loanRepaymentScheduleInstallment.getDueDate().isAfter(toDate)) { + if (DateUtils.isAfter(loanRepaymentScheduleInstallment.getDueDate(), fromDate) + && !DateUtils.isAfter(loanRepaymentScheduleInstallment.getDueDate(), toDate)) { installments.add(loanRepaymentScheduleInstallment.getInstallmentNumber()); } } @@ -5733,7 +5670,7 @@ private void determineFeeDetails(LocalDate fromDate, LocalDate toDate, HashMap loanCharges = new ArrayList<>(); List loanInstallmentCharges = new ArrayList<>(); for (LoanCharge loanCharge : this.getActiveCharges()) { - boolean isDue = fromDate.isEqual(this.getDisbursementDate()) + boolean isDue = DateUtils.isEqual(fromDate, this.getDisbursementDate()) ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, toDate) : loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, toDate); if (isDue) { @@ -5762,7 +5699,7 @@ private void determineFeeDetails(LocalDate fromDate, LocalDate toDate, HashMap transactions, LocalDate effectiveDate) { for (LoanTransaction loanTransaction : transactions) { - if (loanTransaction.getTransactionDate().isEqual(effectiveDate)) { + if (DateUtils.isEqual(effectiveDate, loanTransaction.getTransactionDate())) { return loanTransaction; } } @@ -5771,7 +5708,7 @@ private LoanTransaction getTransactionForDate(List transactions private void reverseTransactionsPostEffectiveDate(List transactions, LocalDate effectiveDate) { for (LoanTransaction loanTransaction : transactions) { - if (loanTransaction.getTransactionDate().isAfter(effectiveDate)) { + if (DateUtils.isAfter(loanTransaction.getTransactionDate(), effectiveDate)) { loanTransaction.reverse(); } } @@ -5813,7 +5750,8 @@ private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO gener return null; } final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); - final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod); + final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory() + .create(this.loanRepaymentScheduleDetail.getLoanScheduleType(), interestMethod); final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final MathContext mc = new MathContext(19, roundingMode); @@ -5837,7 +5775,8 @@ public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final ScheduleGene final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO); - final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod); + final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory() + .create(loanApplicationTerms.getLoanScheduleType(), interestMethod); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategyCode); installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this, @@ -6039,15 +5978,12 @@ public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment(LocalDat if (dueDate != null) { List installments = getRepaymentScheduleInstallments(); for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : installments) { - - if (repaymentScheduleInstallment.getDueDate().isEqual(dueDate)) { + if (DateUtils.isEqual(dueDate, repaymentScheduleInstallment.getDueDate())) { installment = repaymentScheduleInstallment; - break; } } } - return installment; } @@ -6337,15 +6273,13 @@ public ChangedTransactionDetail makeRefundForActiveLoan(final LoanTransaction lo private void validateRefundDateIsAfterLastRepayment(final LocalDate refundTransactionDate) { final LocalDate possibleNextRefundDate = possibleNextRefundDate(); - if (possibleNextRefundDate == null || refundTransactionDate.isBefore(possibleNextRefundDate)) { + if (possibleNextRefundDate == null || DateUtils.isBefore(refundTransactionDate, possibleNextRefundDate)) { throw new InvalidRefundDateException(refundTransactionDate.toString()); } - } private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) { - ChangedTransactionDetail changedTransactionDetail = null; loanLifecycleStateMachine.transition(LoanEvent.LOAN_REFUND, this); @@ -6353,11 +6287,9 @@ private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction l loanTransaction.updateLoan(this); if (getStatus().isOverpaid() || getStatus().isClosed()) { - final String errorMessage = "This refund option is only for active loans "; throw new InvalidLoanStateTransitionException("transaction", "is.exceeding.overpaid.amount", errorMessage, this.totalOverpaid, loanTransaction.getAmount(getCurrency()).getAmount()); - } else if (this.getTotalPaidInRepayments().isZero()) { final String errorMessage = "Cannot refund when no payment has been made"; throw new InvalidLoanStateTransitionException("transaction", "no.payment.yet.made.for.loan", errorMessage); @@ -6366,21 +6298,20 @@ private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction l if (loanTransaction.isNotZero(loanCurrency())) { addLoanTransaction(loanTransaction); } - if (loanTransaction.isNotRefundForActiveLoan()) { final String errorMessage = "A transaction of type refund was expected but not received."; throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.refund.transaction", errorMessage); } final LocalDate loanTransactionDate = loanTransaction.getTransactionDate(); - if (loanTransactionDate.isBefore(getDisbursementDate())) { + if (DateUtils.isBefore(loanTransactionDate, getDisbursementDate())) { final String errorMessage = "The transaction date cannot be before the loan disbursement date: " + getDisbursementDate().toString(); throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage, loanTransactionDate, getDisbursementDate()); } - if (loanTransactionDate.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(loanTransactionDate)) { final String errorMessage = "The transaction date cannot be in the future."; throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate); } @@ -6479,7 +6410,6 @@ public void addTrancheLoanCharge(final Charge charge) { public Map undoLastDisbursal(ScheduleGeneratorDTO scheduleGeneratorDTO, List existingTransactionIds, List existingReversedTransactionIds, Loan loan) { - validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO_LAST); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO_LAST, getDisbursementDate()); @@ -6495,8 +6425,9 @@ public Map undoLastDisbursal(ScheduleGeneratorDTO scheduleGenera loanTransactions = retrieveListOfTransactionsExcludeAccruals(); Collections.reverse(loanTransactions); for (final LoanTransaction previousTransaction : loanTransactions) { - if (lastTransactionDate.isBefore(previousTransaction.getTransactionDate()) && (previousTransaction.isRepaymentLikeType() - || previousTransaction.isWaiver() || previousTransaction.isChargePayment())) { + if (DateUtils.isBefore(lastTransactionDate, previousTransaction.getTransactionDate()) + && (previousTransaction.isRepaymentLikeType() || previousTransaction.isWaiver() + || previousTransaction.isChargePayment())) { throw new UndoLastTrancheDisbursementException(previousTransaction.getId()); } if (previousTransaction.getId().compareTo(lastDisbursalTransaction.getId()) < 0) { @@ -6508,11 +6439,11 @@ public Map undoLastDisbursal(ScheduleGeneratorDTO scheduleGenera updateLoanToLastDisbursalState(disbursementDetail); for (Iterator iterator = this.loanTermVariations.iterator(); iterator.hasNext();) { LoanTermVariations loanTermVariations = iterator.next(); - if ((loanTermVariations.getTermType().isDueDateVariation() && loanTermVariations.fetchDateValue().isAfter(lastTransactionDate)) + if ((loanTermVariations.getTermType().isDueDateVariation() + && DateUtils.isAfter(loanTermVariations.fetchDateValue(), lastTransactionDate)) || (loanTermVariations.getTermType().isEMIAmountVariation() - && loanTermVariations.getTermApplicableFrom().compareTo(lastTransactionDate) == 0 ? Boolean.TRUE - : Boolean.FALSE) - || loanTermVariations.getTermApplicableFrom().isAfter(lastTransactionDate)) { + && DateUtils.isEqual(loanTermVariations.getTermApplicableFrom(), lastTransactionDate)) + || DateUtils.isAfter(loanTermVariations.getTermApplicableFrom(), lastTransactionDate)) { iterator.remove(); } } @@ -6535,8 +6466,8 @@ public Map undoLastDisbursal(ScheduleGeneratorDTO scheduleGenera */ public void reverseExistingTransactionsTillLastDisbursal(LoanTransaction lastDisbursalTransaction) { for (final LoanTransaction transaction : this.loanTransactions) { - if ((transaction.getTransactionDate().compareTo(lastDisbursalTransaction.getTransactionDate()) >= 0) - && (transaction.getId().compareTo(lastDisbursalTransaction.getId()) >= 0) + if (!DateUtils.isBefore(transaction.getTransactionDate(), lastDisbursalTransaction.getTransactionDate()) + && transaction.getId().compareTo(lastDisbursalTransaction.getId()) >= 0 && transaction.isAllowTypeTransactionAtTheTimeOfLastUndo()) { transaction.reverse(); } @@ -6548,11 +6479,10 @@ public void reverseExistingTransactionsTillLastDisbursal(LoanTransaction lastDis Money downPaymentMoney = Money.of(getCurrency(), MathUtil.percentageOf(lastDisbursalTransaction.getAmount(), disbursedAmountPercentageForDownPayment, 19)); - // find the matching down-payment transaction based on date, amount and it also must have downpayment - // installment linked + // find the latest matching down-payment transaction based on date, amount and transaction type Optional downPaymentTransaction = this.loanTransactions.stream() .filter(tr -> tr.getTransactionDate().equals(lastDisbursalTransaction.getTransactionDate()) - && hasAnInstallmentWithDownPayment(tr, downPaymentMoney.getAmount())) + && tr.getTypeOf().isDownPayment() && tr.getAmount().compareTo(downPaymentMoney.getAmount()) == 0) .max(Comparator.comparing(LoanTransaction::getId)); // reverse the down-payment transaction @@ -6560,13 +6490,7 @@ && hasAnInstallmentWithDownPayment(tr, downPaymentMoney.getAmount())) } } - private boolean hasAnInstallmentWithDownPayment(LoanTransaction tr, BigDecimal amount) { - return tr.getAmount().compareTo(amount) == 0 && tr.getLoanTransactionToRepaymentScheduleMappings().stream() - .anyMatch(mapping -> mapping.getLoanRepaymentScheduleInstallment().isDownPayment()); - } - private void updateLoanToLastDisbursalState(LoanDisbursementDetails disbursementDetail) { - for (final LoanCharge charge : getActiveCharges()) { if (charge.isOverdueInstallmentCharge()) { charge.setActive(false); @@ -6641,9 +6565,8 @@ public LocalDate getNextPossibleRepaymentDateForRescheduling() { if (loanDisbursementDetail.actualDisbursementDate() == null) { List installments = getRepaymentScheduleInstallments(); for (final LoanRepaymentScheduleInstallment installment : installments) { - if (installment.getDueDate().isEqual(loanDisbursementDetail.expectedDisbursementDateAsLocalDate()) - || (installment.getDueDate().isAfter(loanDisbursementDetail.expectedDisbursementDateAsLocalDate()) - && installment.isNotFullyPaidOff())) { + if (!DateUtils.isBefore(installment.getDueDate(), loanDisbursementDetail.expectedDisbursementDateAsLocalDate()) + && installment.isNotFullyPaidOff()) { nextRepaymentDate = installment.getDueDate(); break; } @@ -6665,7 +6588,7 @@ public BigDecimal getDerivedAmountForCharge(final LoanCharge loanCharge) { // amount disbursed. if (loanCharge.isSpecifiedDueDate() && this.isMultiDisburmentLoan()) { for (final LoanDisbursementDetails loanDisbursementDetails : this.getDisbursementDetails()) { - if (!loanDisbursementDetails.expectedDisbursementDate().isAfter(loanCharge.getDueDate())) { + if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) { amount = amount.add(loanDisbursementDetails.principal()); } } @@ -6714,11 +6637,11 @@ public Money[] retriveIncomeOutstandingTillDate(final LocalDate paymentDate) { Money fee = Money.zero(currency); Money penalty = Money.zero(currency); for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { - if (!installment.getDueDate().isAfter(paymentDate)) { + if (!DateUtils.isBefore(paymentDate, installment.getDueDate())) { interest = interest.plus(installment.getInterestOutstanding(currency)); penalty = penalty.plus(installment.getPenaltyChargesOutstanding(currency)); fee = fee.plus(installment.getFeeChargesOutstanding(currency)); - } else if (installment.getFromDate().isBefore(paymentDate)) { + } else if (DateUtils.isAfter(paymentDate, installment.getFromDate())) { Money[] balancesForCurrentPeroid = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment); if (balancesForCurrentPeroid[0].isGreaterThan(balancesForCurrentPeroid[5])) { interest = interest.plus(balancesForCurrentPeroid[0]).minus(balancesForCurrentPeroid[5]); @@ -6738,7 +6661,7 @@ public Money[] retriveIncomeOutstandingTillDate(final LocalDate paymentDate) { paidFromFutureInstallments = paidFromFutureInstallments.plus(balancesForCurrentPeroid[4]) .minus(balancesForCurrentPeroid[2]); } - } else if (installment.getDueDate().isAfter(paymentDate)) { + } else { paidFromFutureInstallments = paidFromFutureInstallments.plus(installment.getInterestPaid(currency)) .plus(installment.getPenaltyChargesPaid(currency)).plus(installment.getFeeChargesPaid(currency)); } @@ -6807,7 +6730,7 @@ public Money[] retriveIncomeForOverlappingPeriod(final LocalDate paymentDate) { final MonetaryCurrency currency = getCurrency(); balances[0] = balances[1] = balances[2] = Money.zero(currency); for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { - if (installment.getDueDate().isEqual(paymentDate)) { + if (DateUtils.isEqual(paymentDate, installment.getDueDate())) { Money interest = installment.getInterestCharged(currency); Money fee = installment.getFeeChargesCharged(currency); Money penalty = installment.getPenaltyChargesCharged(currency); @@ -6815,7 +6738,8 @@ public Money[] retriveIncomeForOverlappingPeriod(final LocalDate paymentDate) { balances[1] = fee; balances[2] = penalty; break; - } else if (installment.getDueDate().isAfter(paymentDate) && installment.getFromDate().isBefore(paymentDate)) { + } else if (DateUtils.isAfter(paymentDate, installment.getFromDate()) + && DateUtils.isBefore(paymentDate, installment.getDueDate())) { balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment); break; } @@ -6839,7 +6763,7 @@ public Money[] getReceivableIncome(final LocalDate tillDate) { Money[] receivables = new Money[3]; for (final LoanTransaction transaction : this.loanTransactions) { if (transaction.isNotReversed() && !transaction.isRepaymentAtDisbursement() && !transaction.isDisbursement() - && !transaction.getTransactionDate().isAfter(tillDate)) { + && !DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) { if (transaction.isAccrual()) { receivableInterest = receivableInterest.plus(transaction.getInterestPortion(currency)); receivableFee = receivableFee.plus(transaction.getFeeChargesPortion(currency)); @@ -6868,7 +6792,7 @@ public Money[] getReceivableIncome(final LocalDate tillDate) { public void reverseAccrualsAfter(final LocalDate tillDate) { for (final LoanTransaction transaction : this.loanTransactions) { - if (transaction.isAccrual() && transaction.getTransactionDate().isAfter(tillDate)) { + if (transaction.isAccrual() && DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) { transaction.reverse(); } } @@ -6876,7 +6800,6 @@ public void reverseAccrualsAfter(final LocalDate tillDate) { public ChangedTransactionDetail handleForeClosureTransactions(final LoanTransaction repaymentTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final ScheduleGeneratorDTO scheduleGeneratorDTO) { - LoanEvent event = LoanEvent.LOAN_FORECLOSURE; validateAccountStatus(event); validateForForeclosure(repaymentTransaction.getTransactionDate()); @@ -6885,33 +6808,7 @@ public ChangedTransactionDetail handleForeClosureTransactions(final LoanTransact return handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO); } - public Money retrieveAccruedAmountAfterDate(final LocalDate tillDate) { - Money totalAmountAccrued = Money.zero(getCurrency()); - Money actualAmountTobeAccrued = Money.zero(getCurrency()); - for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { - totalAmountAccrued = totalAmountAccrued.plus(installment.getInterestAccrued(getCurrency())); - - if (tillDate.isAfter(installment.getFromDate()) && tillDate.isBefore(installment.getDueDate())) { - int daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate())); - int tillDays = Math.toIntExact(ChronoUnit.DAYS.between(installment.getFromDate(), tillDate)); - double interest = calculateInterestForDays(daysInPeriod, installment.getInterestCharged(getCurrency()).getAmount(), - tillDays); - actualAmountTobeAccrued = actualAmountTobeAccrued.plus(interest); - } else if ((tillDate.isAfter(installment.getFromDate()) && tillDate.isEqual(installment.getDueDate())) - || (tillDate.isEqual(installment.getFromDate()) && tillDate.isEqual(installment.getDueDate())) - || (tillDate.isAfter(installment.getFromDate()) && tillDate.isAfter(installment.getDueDate()))) { - actualAmountTobeAccrued = actualAmountTobeAccrued.plus(installment.getInterestAccrued(getCurrency())); - } - } - Money accredAmountAfterDate = totalAmountAccrued.minus(actualAmountTobeAccrued); - if (accredAmountAfterDate.isLessThanZero()) { - accredAmountAfterDate = Money.zero(getCurrency()); - } - return accredAmountAfterDate; - } - public void validateForForeclosure(final LocalDate transactionDate) { - if (isInterestRecalculationEnabledForProduct()) { final String defaultUserMessage = "The loan with interest recalculation enabled cannot be foreclosed."; throw new LoanForeclosureException("loan.with.interest.recalculation.enabled.cannot.be.foreclosured", defaultUserMessage, @@ -6925,8 +6822,8 @@ public void validateForForeclosure(final LocalDate transactionDate) { throw new LoanForeclosureException("loan.foreclosure.transaction.date.is.in.future", defaultUserMessage, transactionDate); } - if (lastUserTransactionDate.isAfter(transactionDate)) { - final String defaultUserMessage = "The transactionDate cannot be in the future."; + if (DateUtils.isBefore(transactionDate, lastUserTransactionDate)) { + final String defaultUserMessage = "The transactionDate cannot be earlier than the last transaction date."; throw new LoanForeclosureException("loan.foreclosure.transaction.date.cannot.before.the.last.transaction.date", defaultUserMessage, transactionDate); } @@ -6939,10 +6836,10 @@ public void updateInstallmentsPostDate(LocalDate transactionDate) { Money[] balances = retriveIncomeForOverlappingPeriod(transactionDate); boolean isInterestComponent = true; for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { - if (!installment.getDueDate().isBefore(transactionDate)) { + if (!DateUtils.isAfter(transactionDate, installment.getDueDate())) { totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency)); newInstallments.remove(installment); - if (installment.getDueDate().isEqual(transactionDate)) { + if (DateUtils.isEqual(transactionDate, installment.getDueDate())) { isInterestComponent = false; } } @@ -6977,7 +6874,7 @@ public void updateInstallmentsPostDate(LocalDate transactionDate) { Set charges = this.getActiveCharges(); int penaltyWaitPeriod = 0; for (LoanCharge loanCharge : charges) { - if (loanCharge.getDueLocalDate() != null && loanCharge.getDueLocalDate().isAfter(transactionDate)) { + if (DateUtils.isAfter(loanCharge.getDueLocalDate(), transactionDate)) { loanCharge.setActive(false); } else if (loanCharge.getDueLocalDate() == null) { recalculateLoanCharge(loanCharge, penaltyWaitPeriod); @@ -6989,7 +6886,7 @@ public void updateInstallmentsPostDate(LocalDate transactionDate) { if (loanTransaction.isChargesWaiver()) { for (LoanChargePaidBy chargePaidBy : loanTransaction.getLoanChargesPaid()) { if ((chargePaidBy.getLoanCharge().isDueDateCharge() - && chargePaidBy.getLoanCharge().getDueLocalDate().isAfter(transactionDate)) + && DateUtils.isBefore(transactionDate, chargePaidBy.getLoanCharge().getDueLocalDate())) || (chargePaidBy.getLoanCharge().isInstalmentFee() && chargePaidBy.getInstallmentNumber() != null && chargePaidBy.getInstallmentNumber() > installmentNumber)) { loanTransaction.reverse(); @@ -7243,4 +7140,13 @@ public String getTransactionProcessingStrategyCode() { public String getTransactionProcessingStrategyName() { return transactionProcessingStrategyName; } + + public boolean isEnableInstallmentLevelDelinquency() { + return this.enableInstallmentLevelDelinquency; + } + + public void updateEnableInstallmentLevelDelinquency(boolean enableInstallmentLevelDelinquency) { + this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 0eb0c553b94..252886de6fd 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -363,13 +363,11 @@ public void update(final BigDecimal amount, final LocalDate dueDate, final Integ if (this.loan != null) { switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { case PERCENT_OF_AMOUNT: - // If charge type is specified due date and loan is multi - // disburment loan. - // Then we need to get as of this loan charge due date how - // much amount disbursed. + // If charge type is specified due date and loan is multi disburment loan. + // Then we need to get as of this loan charge due date how much amount disbursed. if (this.loan.isMultiDisburmentLoan() && this.isSpecifiedDueDate()) { for (final LoanDisbursementDetails loanDisbursementDetails : this.loan.getDisbursementDetails()) { - if (!loanDisbursementDetails.expectedDisbursementDate().isAfter(this.getDueDate())) { + if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), this.getDueDate())) { amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.principal()); } } @@ -635,13 +633,12 @@ public boolean isDueForCollectionFromIncludingAndUpToAndIncluding(final LocalDat private boolean occursOnDayFromExclusiveAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive, final LocalDate target) { - return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive); + return DateUtils.isAfter(target, fromNotInclusive) && !DateUtils.isAfter(target, upToAndInclusive); } private boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive, final LocalDate target) { - return target != null && (target.isEqual(fromAndInclusive) || target.isAfter(fromAndInclusive)) - && !target.isAfter(upToAndInclusive); + return target != null && !DateUtils.isBefore(target, fromAndInclusive) && !DateUtils.isAfter(target, upToAndInclusive); } public boolean isFeeCharge() { @@ -792,8 +789,9 @@ public void updateAmount(final BigDecimal amount) { public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() { LoanInstallmentCharge unpaidChargePerInstallment = null; for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) { - if (loanChargePerInstallment.isPending() && (unpaidChargePerInstallment == null || unpaidChargePerInstallment - .getRepaymentInstallment().getDueDate().isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { + if (loanChargePerInstallment.isPending() && (unpaidChargePerInstallment == null + || DateUtils.isAfter(unpaidChargePerInstallment.getRepaymentInstallment().getDueDate(), + loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { unpaidChargePerInstallment = loanChargePerInstallment; } } @@ -802,7 +800,7 @@ public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() { public LoanInstallmentCharge getInstallmentLoanCharge(final LocalDate periodDueDate) { for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) { - if (periodDueDate.isEqual(loanChargePerInstallment.getRepaymentInstallment().getDueDate())) { + if (DateUtils.isEqual(periodDueDate, loanChargePerInstallment.getRepaymentInstallment().getDueDate())) { return loanChargePerInstallment; } } @@ -974,8 +972,9 @@ public LoanInstallmentCharge getLastPaidOrPartiallyPaidInstallmentLoanCharge(Mon Money outstanding = Money.of(currency, loanChargePerInstallment.getAmountOutstanding()); final boolean partiallyPaid = outstanding.isGreaterThanZero() && outstanding.isLessThan(loanChargePerInstallment.getAmount(currency)); - if ((partiallyPaid || loanChargePerInstallment.isPaid()) && (paidChargePerInstallment == null || paidChargePerInstallment - .getRepaymentInstallment().getDueDate().isBefore(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { + if ((partiallyPaid || loanChargePerInstallment.isPaid()) && (paidChargePerInstallment == null + || DateUtils.isBefore(paidChargePerInstallment.getRepaymentInstallment().getDueDate(), + loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { paidChargePerInstallment = loanChargePerInstallment; } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java index abcac805144..a37fd0e2c52 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java @@ -27,6 +27,7 @@ import java.time.LocalDate; import java.util.Objects; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; @Entity @@ -71,16 +72,11 @@ public void updateLoan(final Loan loan) { @Override public boolean equals(final Object obj) { - if (!(obj instanceof LoanDisbursementDetails)) { + if (!(obj instanceof LoanDisbursementDetails loanDisbursementDetails)) { return false; } - final LoanDisbursementDetails loanDisbursementDetails = (LoanDisbursementDetails) obj; - if (loanDisbursementDetails.principal.equals(this.principal) - && loanDisbursementDetails.expectedDisbursementDate.compareTo(this.expectedDisbursementDate) == 0 ? Boolean.TRUE - : Boolean.FALSE) { - return true; - } - return false; + return loanDisbursementDetails.principal.equals(this.principal) + && DateUtils.isEqual(loanDisbursementDetails.expectedDisbursementDate, this.expectedDisbursementDate); } @Override diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java index 6070eca230b..8bb9d082b85 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java @@ -25,6 +25,7 @@ import jakarta.persistence.Table; import java.time.LocalDate; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.staff.domain.Staff; @Entity @@ -72,16 +73,16 @@ public void updateEndDate(final LocalDate endDate) { this.endDate = endDate; } - public boolean matchesStartDateOf(final LocalDate matchingDate) { - return getStartDate().isEqual(matchingDate); - } - public LocalDate getStartDate() { return this.startDate; } - public boolean hasStartDateBefore(final LocalDate matchingDate) { - return matchingDate.isBefore(getStartDate()); + public boolean matchesStartDateOf(final LocalDate matchingDate) { + return DateUtils.isEqual(matchingDate, getStartDate()); + } + + public boolean isBeforeStartDate(final LocalDate matchingDate) { + return DateUtils.isBefore(matchingDate, getStartDate()); } public boolean isCurrentRecord() { @@ -95,7 +96,7 @@ public boolean isCurrentRecord() { * @return */ public boolean isEndDateAfter(final LocalDate compareDate) { - return this.endDate.isAfter(compareDate); + return DateUtils.isAfter(this.endDate, compareDate); } public LocalDate getEndDate() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 69852054f72..fd7f81936e6 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -31,6 +31,7 @@ import java.util.HashSet; import java.util.Set; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; @@ -418,6 +419,11 @@ public void resetAccrualComponents() { this.penaltyAccrued = null; } + public void resetChargesCharged() { + this.feeChargesCharged = null; + this.penaltyCharges = null; + } + public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); @@ -625,7 +631,7 @@ public Money writeOffOutstandingPenaltyCharges(final LocalDate transactionDate, } public boolean isOverdueOn(final LocalDate date) { - return getDueDate().isBefore(date); + return DateUtils.isAfter(date, getDueDate()); } public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff, @@ -638,6 +644,17 @@ public void updateChargePortion(final Money feeChargesDue, final Money feeCharge this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount()); } + public void addToChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff, + final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) { + this.feeChargesCharged = defaultToNullIfZero(feeChargesDue.plus(this.feeChargesCharged).getAmount()); + this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.plus(this.feeChargesWaived).getAmount()); + this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.plus(this.feeChargesWrittenOff).getAmount()); + this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.plus(this.penaltyCharges).getAmount()); + this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.plus(this.penaltyChargesWaived).getAmount()); + this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.plus(this.penaltyChargesWrittenOff).getAmount()); + checkIfRepaymentPeriodObligationsAreMet(getObligationsMetOnDate(), feeChargesDue.getCurrency()); + } + public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) { this.interestAccrued = defaultToNullIfZero(interest.getAmount()); this.feeAccrued = defaultToNullIfZero(feeCharges.getAmount()); @@ -665,11 +682,11 @@ private Money asMoney(final BigDecimal decimal, final MonetaryCurrency currency) } private boolean isInAdvance(final LocalDate transactionDate) { - return transactionDate.isBefore(getDueDate()); + return DateUtils.isBefore(transactionDate, getDueDate()); } private boolean isLatePayment(final LocalDate transactionDate) { - return transactionDate.isAfter(getDueDate()); + return DateUtils.isAfter(transactionDate, getDueDate()); } private void checkIfRepaymentPeriodObligationsAreMet(final LocalDate transactionDate, final MonetaryCurrency currency) { @@ -928,6 +945,11 @@ public boolean isFirstPeriod() { return (this.installmentNumber == 1); } + public boolean isInPeriod(LocalDate date) { + return (isFirstPeriod() ? !DateUtils.isBefore(date, getFromDate()) : DateUtils.isAfter(date, getFromDate())) + && !DateUtils.isAfter(date, getDueDate()); + } + public Set getLoanTransactionToRepaymentScheduleMappings() { return this.loanTransactionToRepaymentScheduleMappings; } @@ -936,4 +958,13 @@ public boolean isDownPayment() { return isDownPayment; } + public void resetBalances() { + resetDerivedComponents(); + resetPrincipalDue(); + resetChargesCharged(); + } + + public void resetPrincipalDue() { + this.principal = null; + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java index 2d73f5295a2..4c032e3cdd6 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java @@ -22,8 +22,11 @@ import java.time.LocalDate; import java.util.List; import java.util.Set; +import java.util.function.Predicate; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.jetbrains.annotations.NotNull; /** * A wrapper around loan schedule related data exposing needed behaviour by loan. @@ -51,18 +54,20 @@ public void reprocess(final MonetaryCurrency currency, final LocalDate disbursem final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod); - final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), - loanCharges, currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod); - final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), - loanCharges, currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod); + final Money feeChargesWaivedForRepaymentPeriod = cumulativeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, + currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod, feeCharge()); + final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeChargesWrittenOffWithin(startDate, period.getDueDate(), + loanCharges, currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod, feeCharge()); final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), loanCharges, currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod); - final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), - loanCharges, currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod); - final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, - period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod); + final Money penaltyChargesWaivedForRepaymentPeriod = cumulativeChargesWaivedWithin(startDate, period.getDueDate(), + loanCharges, currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod, + LoanCharge::isPenaltyCharge); + final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativeChargesWrittenOffWithin(startDate, period.getDueDate(), + loanCharges, currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod, + LoanCharge::isPenaltyCharge); period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, @@ -80,24 +85,9 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L Money cumulative = Money.zero(monetaryCurrency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - if (loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount()) - .add(period.getInterestCharged(monetaryCurrency).getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(period.getInterestCharged(monetaryCurrency).getAmount()); - } else { - amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount()); - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - cumulative = cumulative.plus(loanChargeAmt); - } else { - cumulative = cumulative.plus(loanCharge.amountOrPercentage()); - } + cumulative = cumulative.plus(getInstallmentFee(monetaryCurrency, period, loanCharge)); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -114,7 +104,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L if (loanCharge.getLoan() != null && loanCharge.isSpecifiedDueDate() && loanCharge.getLoan().isMultiDisburmentLoan()) { for (final LoanDisbursementDetails loanDisbursementDetails : loanCharge.getLoan().getDisbursementDetails()) { - if (!loanDisbursementDetails.expectedDisbursementDate().isAfter(loanCharge.getDueDate())) { + if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) { amount = amount.add(loanDisbursementDetails.principal()); } } @@ -133,16 +123,15 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L return cumulative; } - private Money cumulativeFeeChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, - boolean isFirstPeriod) { + private Money cumulativeChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, + final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, + Predicate predicate) { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + if (predicate.test(loanCharge)) { + boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { @@ -157,16 +146,15 @@ private Money cumulativeFeeChargesWaivedWithin(final LocalDate periodStart, fina return cumulative; } - private Money cumulativeFeeChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, + private Money cumulativeChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, - boolean isFirstPeriod) { + boolean isFirstPeriod, Predicate chargePredicate) { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + if (chargePredicate.test(loanCharge)) { + boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { @@ -181,6 +169,15 @@ private Money cumulativeFeeChargesWrittenOffWithin(final LocalDate periodStart, return cumulative; } + private Predicate feeCharge() { + return loanCharge -> loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement(); + } + + private boolean loanChargeIsDue(LocalDate periodStart, LocalDate periodEnd, boolean isFirstPeriod, LoanCharge loanCharge) { + return isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) + : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + } + private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { @@ -189,24 +186,9 @@ private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, fin for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - if (loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(period.getPrincipal(currency).getAmount()) - .add(period.getInterestCharged(currency).getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(period.getInterestCharged(currency).getAmount()); - } else { - amount = amount.add(period.getPrincipal(currency).getAmount()); - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - cumulative = cumulative.plus(loanChargeAmt); - } else { - cumulative = cumulative.plus(loanCharge.amountOrPercentage()); - } + cumulative = cumulative.plus(getInstallmentFee(currency, period, loanCharge)); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -229,51 +211,28 @@ private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, fin return cumulative; } - private Money cumulativePenaltyChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, - boolean isFirstPeriod) { - - Money cumulative = Money.zero(currency); - - for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isPenaltyCharge()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); - if (loanChargePerInstallment != null) { - cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency)); - } - } else if (isDue) { - cumulative = cumulative.plus(loanCharge.getAmountWaived(currency)); - } - } + private BigDecimal getInstallmentFee(MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, LoanCharge loanCharge) { + if (loanCharge.getChargeCalculation().isPercentageBased()) { + BigDecimal amount = BigDecimal.ZERO; + amount = getBaseAmount(currency, period, loanCharge, amount); + return amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + } else { + return loanCharge.amountOrPercentage(); } - - return cumulative; } - private Money cumulativePenaltyChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, - final boolean isFirstPeriod) { - - Money cumulative = Money.zero(currency); - - for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isPenaltyCharge()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); - if (loanChargePerInstallment != null) { - cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency)); - } - } else if (isDue) { - cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency)); - } - } + @NotNull + private BigDecimal getBaseAmount(MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, LoanCharge loanCharge, + BigDecimal amount) { + if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { + amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount()) + .add(period.getInterestCharged(monetaryCurrency).getAmount()); + } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { + amount = amount.add(period.getInterestCharged(monetaryCurrency).getAmount()); + } else { + amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount()); } - - return cumulative; + return amount; } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java index 5bd79a81482..947d175407a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryWrapper.java @@ -21,6 +21,7 @@ import java.time.LocalDate; import java.util.List; import java.util.Set; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.springframework.stereotype.Component; @@ -230,7 +231,7 @@ public LocalDate determineOverdueSinceDateFrom(final List mappings) { Collection retainMappings = new ArrayList<>(); for (LoanTransactionToRepaymentScheduleMapping updatedrepaymentScheduleMapping : mappings) { @@ -962,6 +944,18 @@ public BigDecimal getAmount() { return amount; } + public boolean isBefore(final LocalDate date) { + return DateUtils.isBefore(getTransactionDate(), date); + } + + public boolean isAfter(final LocalDate date) { + return DateUtils.isAfter(getTransactionDate(), date); + } + + public boolean isOn(final LocalDate date) { + return DateUtils.isEqual(getTransactionDate(), date); + } + // TODO missing hashCode(), equals(Object obj), but probably OK as long as // this is never stored in a Collection. } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionComparator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionComparator.java index fb4a5109870..ead9e19a6cf 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionComparator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionComparator.java @@ -19,47 +19,40 @@ package org.apache.fineract.portfolio.loanaccount.domain; import java.util.Comparator; +import org.apache.fineract.infrastructure.core.service.DateUtils; /** * Sort loan transactions by transaction date, created date and transaction type placing */ public class LoanTransactionComparator implements Comparator { + public static final LoanTransactionComparator INSTANCE = new LoanTransactionComparator(); + @Override public int compare(final LoanTransaction o1, final LoanTransaction o2) { - int compareResult = 0; - final int comparsion = o1.getTransactionDate().compareTo(o2.getTransactionDate()); - /** - * For transactions bearing the same transaction date, we sort transactions based on created date (when - * available) after which sorting for waivers takes place - **/ - if (comparsion == 0) { - int comparisonBasedOnCreatedDate = 0; - if (o1.isIncomePosting() && o2.isNotIncomePosting()) { - compareResult = -1; - } else if (o1.isNotIncomePosting() && o2.isIncomePosting()) { - compareResult = 1; - } else { - compareResult = 0; - } - if (o1.getCreatedDateTime() != null && o2.getCreatedDateTime() != null) { - comparisonBasedOnCreatedDate = o1.getCreatedDateTime().compareTo(o2.getCreatedDateTime()); - } - // equal transaction dates - if (comparisonBasedOnCreatedDate == 0) { - if (o1.isWaiver() && o2.isNotWaiver()) { - compareResult = -1; - } else if (o1.isNotWaiver() && o2.isWaiver()) { - compareResult = 1; - } else { - compareResult = 0; - } - } - } else { - compareResult = comparsion; + int result = DateUtils.compare(o1.getTransactionDate(), o2.getTransactionDate()); + if (result != 0) { + return result; } - - return compareResult; + // For transactions bearing the same transaction date, we sort transactions based on created date (when + // available) after which sorting for waivers takes place + result = o1.getCreatedDate().isPresent() + ? (o2.getCreatedDate().isPresent() ? DateUtils.compare(o1.getCreatedDateTime(), o2.getCreatedDateTime()) : -1) + : (o2.getCreatedDate().isPresent() ? 1 : 0); + if (result != 0) { + return result; + } + // equal transaction dates + if (o1.isIncomePosting() != o2.isIncomePosting()) { + return o1.isIncomePosting() ? -1 : 1; + } + if (o1.isWaiver() != o2.isWaiver()) { + return o1.isWaiver() ? -1 : 1; + } + result = o1.getId() != null ? (o2.getId() != null ? Long.compare(o1.getId(), o2.getId()) : -1) : (o2.getId() != null ? 1 : 0); + if (result != 0) { + return result; + } + return 0; } - } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java new file mode 100644 index 00000000000..c119dfb49af --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java @@ -0,0 +1,216 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.domain; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.function.Predicate; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.jetbrains.annotations.NotNull; + +/** + * A wrapper around loan schedule related data exposing needed behaviour by loan. + */ +public class SingleLoanChargeRepaymentScheduleProcessingWrapper { + + public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, + final List repaymentPeriods, LoanCharge loanCharge) { + + Money totalInterest = Money.zero(currency); + Money totalPrincipal = Money.zero(currency); + for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) { + totalInterest = totalInterest.plus(installment.getInterestCharged(currency)); + totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency)); + } + LocalDate startDate = disbursementDate; + for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { + + if (!period.isDownPayment()) { + + boolean isFirstNonDownPaymentPeriod = repaymentPeriods.stream() + .filter(repaymentPeriod -> repaymentPeriod.getInstallmentNumber() < period.getInstallmentNumber()) + .allMatch(LoanRepaymentScheduleInstallment::isDownPayment); + + final Money feeChargesDueForRepaymentPeriod = feeChargesDueWithin(startDate, period.getDueDate(), loanCharge, currency, + period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod); + final Money feeChargesWaivedForRepaymentPeriod = chargesWaivedWithin(startDate, period.getDueDate(), loanCharge, currency, + !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod, feeCharge()); + final Money feeChargesWrittenOffForRepaymentPeriod = loanChargesWrittenOffWithin(startDate, period.getDueDate(), loanCharge, + currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod, feeCharge()); + + final Money penaltyChargesDueForRepaymentPeriod = penaltyChargesDueWithin(startDate, period.getDueDate(), loanCharge, + currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), + isFirstNonDownPaymentPeriod); + final Money penaltyChargesWaivedForRepaymentPeriod = chargesWaivedWithin(startDate, period.getDueDate(), loanCharge, + currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod, LoanCharge::isPenaltyCharge); + final Money penaltyChargesWrittenOffForRepaymentPeriod = loanChargesWrittenOffWithin(startDate, period.getDueDate(), + loanCharge, currency, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod, + LoanCharge::isPenaltyCharge); + + period.addToChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, + feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, + penaltyChargesWrittenOffForRepaymentPeriod); + + startDate = period.getDueDate(); + } + } + } + + private Money feeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, + final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { + + if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { + boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + return Money.of(monetaryCurrency, getInstallmentFee(monetaryCurrency, period, loanCharge)); + } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { + return Money.of(monetaryCurrency, loanCharge.chargeAmount()); + } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { + amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); + } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { + amount = amount.add(totalInterest.getAmount()); + } else { + // If charge type is specified due date and loan is + // multi disburment loan. + // Then we need to get as of this loan charge due date + // how much amount disbursed. + if (loanCharge.getLoan() != null && loanCharge.isSpecifiedDueDate() && loanCharge.getLoan().isMultiDisburmentLoan()) { + for (final LoanDisbursementDetails loanDisbursementDetails : loanCharge.getLoan().getDisbursementDetails()) { + if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) { + amount = amount.add(loanDisbursementDetails.principal()); + } + } + } else { + amount = amount.add(totalPrincipal.getAmount()); + } + } + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + return Money.of(monetaryCurrency, loanChargeAmt); + } else if (isDue) { + return Money.of(monetaryCurrency, loanCharge.amount()); + } + } + return Money.zero(monetaryCurrency); + } + + private Money chargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, + Predicate predicate) { + + if (predicate.test(loanCharge)) { + boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); + if (loanChargePerInstallment != null) { + return loanChargePerInstallment.getAmountWaived(currency); + } + } else if (isDue) { + return loanCharge.getAmountWaived(currency); + } + } + + return Money.zero(currency); + } + + private Money loanChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, + Predicate chargePredicate) { + if (chargePredicate.test(loanCharge)) { + boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); + if (loanChargePerInstallment != null) { + return loanChargePerInstallment.getAmountWrittenOff(currency); + } + } else if (isDue) { + return loanCharge.getAmountWrittenOff(currency); + } + } + return Money.zero(currency); + } + + private Predicate feeCharge() { + return loanCharge -> loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement(); + } + + private boolean loanChargeIsDue(LocalDate periodStart, LocalDate periodEnd, boolean isFirstPeriod, LoanCharge loanCharge) { + return isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) + : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + } + + private Money penaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest, + boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { + + if (loanCharge.isPenaltyCharge()) { + boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + return Money.of(currency, getInstallmentFee(currency, period, loanCharge)); + } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { + return Money.of(currency, loanCharge.chargeAmount()); + } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { + amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); + } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { + amount = amount.add(totalInterest.getAmount()); + } else { + amount = amount.add(totalPrincipal.getAmount()); + } + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + return Money.of(currency, loanChargeAmt); + } else if (isDue) { + return Money.of(currency, loanCharge.amount()); + } + } + + return Money.zero(currency); + } + + private BigDecimal getInstallmentFee(MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, LoanCharge loanCharge) { + if (loanCharge.getChargeCalculation().isPercentageBased()) { + BigDecimal amount = BigDecimal.ZERO; + amount = getBaseAmount(currency, period, loanCharge, amount); + return amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + } else { + return loanCharge.amountOrPercentage(); + } + } + + @NotNull + private BigDecimal getBaseAmount(MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, LoanCharge loanCharge, + BigDecimal amount) { + if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { + amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount()) + .add(period.getInterestCharged(monetaryCurrency).getAmount()); + } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { + amount = amount.add(period.getInterestCharged(monetaryCurrency).getAmount()); + } else { + amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount()); + } + return amount; + } + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java index cbd789b0282..fc6b5c46885 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java @@ -72,6 +72,7 @@ public final class LoanSchedulePeriodData { private final BigDecimal totalActualCostOfLoanForPeriod; private final BigDecimal totalInstallmentAmountForPeriod; private final BigDecimal totalCredits; + private final Boolean downPaymentPeriod; public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed, final BigDecimal feeChargesDueAtTimeOfDisbursement, final boolean isDisbursed) { @@ -96,10 +97,10 @@ public static LoanSchedulePeriodData downPaymentOnlyPeriod(final Integer periodN return new LoanSchedulePeriodData(periodNumber, periodDate, periodDate, principalDue, principalOutstanding); } - public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnings("unused") final Long loanId, - final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate, - final boolean complete, final BigDecimal principalOriginalDue, final BigDecimal principalPaid, - final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding, final BigDecimal outstandingPrincipalBalanceOfLoan, + public static LoanSchedulePeriodData periodWithPayments(@SuppressWarnings("unused") final Long loanId, final Integer periodNumber, + final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate, final boolean complete, + final BigDecimal principalOriginalDue, final BigDecimal principalPaid, final BigDecimal principalWrittenOff, + final BigDecimal principalOutstanding, final BigDecimal outstandingPrincipalBalanceOfLoan, final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff, final BigDecimal interestOutstanding, final BigDecimal feeChargesDue, final BigDecimal feeChargesPaid, final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff, @@ -108,7 +109,7 @@ public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnin final BigDecimal totalDueForPeriod, final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod, final BigDecimal totalPaidLateForPeriod, final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding, final BigDecimal totalActualCostOfLoanForPeriod, - final BigDecimal totalInstallmentAmountForPeriod, final BigDecimal totalCredits) { + final BigDecimal totalInstallmentAmountForPeriod, final BigDecimal totalCredits, final boolean isDownPayment) { return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, @@ -116,7 +117,7 @@ public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnin feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding, - totalActualCostOfLoanForPeriod, totalInstallmentAmountForPeriod, totalCredits); + totalActualCostOfLoanForPeriod, totalInstallmentAmountForPeriod, totalCredits, isDownPayment); } public static LoanSchedulePeriodData withPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData, final boolean complete, @@ -137,7 +138,7 @@ public static LoanSchedulePeriodData withPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData.totalPaidLateForPeriod, loanSchedulePeriodData.totalWaivedForPeriod, loanSchedulePeriodData.totalWrittenOffForPeriod, loanSchedulePeriodData.totalOutstandingForPeriod, loanSchedulePeriodData.totalActualCostOfLoanForPeriod, loanSchedulePeriodData.totalInstallmentAmountForPeriod, - loanSchedulePeriodData.totalCredits); + loanSchedulePeriodData.totalCredits, loanSchedulePeriodData.getDownPaymentPeriod()); } /* @@ -199,12 +200,9 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalOutstandingForPeriod = this.feeChargesOutstanding; this.totalActualCostOfLoanForPeriod = this.feeChargesDue; this.totalInstallmentAmountForPeriod = null; - if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) { - this.totalOverdue = this.totalOutstandingForPeriod; - } else { - this.totalOverdue = null; - } + this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; + this.downPaymentPeriod = false; } /* @@ -261,16 +259,15 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalOutstandingForPeriod = totalDueForPeriod; this.totalActualCostOfLoanForPeriod = interestDueOnPrincipalOutstanding.add(feeChargesDueForPeriod); this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod; - - if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) { - this.totalOverdue = this.totalOutstandingForPeriod; - } else { - this.totalOverdue = null; - } + this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; + this.downPaymentPeriod = false; } // TODO refactor the class to builder pattern + /* + * used for down payment only period when creating an empty loan schedule for preview etc + */ private LoanSchedulePeriodData(Integer periodNumber, LocalDate fromDate, LocalDate dueDate, BigDecimal principalDue, BigDecimal principalOutstanding) { this.period = periodNumber; @@ -316,13 +313,9 @@ private LoanSchedulePeriodData(Integer periodNumber, LocalDate fromDate, LocalDa this.totalOutstandingForPeriod = totalDueForPeriod; this.totalActualCostOfLoanForPeriod = null; this.totalInstallmentAmountForPeriod = totalDueForPeriod; - - if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) { - this.totalOverdue = this.totalOutstandingForPeriod; - } else { - this.totalOverdue = null; - } + this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; + this.downPaymentPeriod = true; } /* @@ -341,7 +334,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod, final BigDecimal totalPaidLateForPeriod, final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding, final BigDecimal totalActualCostOfLoanForPeriod, final BigDecimal totalInstallmentAmountForPeriod, - final BigDecimal totalCredits) { + final BigDecimal totalCredits, final boolean isDownPayment) { this.period = periodNumber; this.fromDate = fromDate; this.dueDate = dueDate; @@ -389,13 +382,9 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalOutstandingForPeriod = totalOutstanding; this.totalActualCostOfLoanForPeriod = totalActualCostOfLoanForPeriod; this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod; - - if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) { - this.totalOverdue = this.totalOutstandingForPeriod; - } else { - this.totalOverdue = null; - } + this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = totalCredits; + this.downPaymentPeriod = isDownPayment; } private BigDecimal defaultToZeroIfNull(final BigDecimal possibleNullValue) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index a8fee74d871..ce20a975aa8 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; +import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; @@ -26,6 +27,7 @@ import java.util.List; import java.util.Set; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; @@ -215,6 +217,8 @@ public final class LoanApplicationTerms { private RepaymentStartDateType repaymentStartDateType; private LocalDate submittedOnDate; private boolean isScheduleExtensionForDownPaymentDisabled; + private Money disbursedPrincipal; + private final LoanScheduleType loanScheduleType; public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, @@ -241,7 +245,8 @@ public static LoanApplicationTerms assembleFrom(final ApplicationCurrency curren final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans, final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final Boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType, - final LocalDate submittedOnDate, final Boolean isScheduleExtensionForDownPaymentDisabled) { + final LocalDate submittedOnDate, final Boolean isScheduleExtensionForDownPaymentDisabled, + final LoanScheduleType loanScheduleType) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; @@ -259,7 +264,8 @@ public static LoanApplicationTerms assembleFrom(final ApplicationCurrency curren isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment, - isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled); + isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled, + loanScheduleType); } @@ -315,6 +321,7 @@ public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applic isAutoRepaymentForDownPaymentEnabled = loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment(); isScheduleExtensionForDownPaymentDisabled = loanProductRelatedDetail.isDisableScheduleExtensionForDownPayment(); } + LoanScheduleType loanScheduleType = loanProductRelatedDetail.getLoanScheduleType(); return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, @@ -330,7 +337,8 @@ public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applic allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment, - isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled); + isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled, + loanScheduleType); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -359,7 +367,7 @@ private LoanApplicationTerms(final ApplicationCurrency currency, final Integer l final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, - final boolean isScheduleExtensionForDownPaymentDisabled) { + final boolean isScheduleExtensionForDownPaymentDisabled, final LoanScheduleType loanScheduleType) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; @@ -379,6 +387,7 @@ private LoanApplicationTerms(final ApplicationCurrency currency, final Integer l this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion; this.principal = principal; + this.disbursedPrincipal = principal; this.expectedDisbursementDate = expectedDisbursementDate; this.repaymentsStartingFromDate = repaymentsStartingFromDate; this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; @@ -442,6 +451,7 @@ private LoanApplicationTerms(final ApplicationCurrency currency, final Integer l this.repaymentStartDateType = repaymentStartDateType; this.submittedOnDate = submittedOnDate; this.isScheduleExtensionForDownPaymentDisabled = isScheduleExtensionForDownPaymentDisabled; + this.loanScheduleType = loanScheduleType; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -596,7 +606,7 @@ private LocalDate getPeriodEndDate(final LocalDate startDate) { } public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator, - final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc, + final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc, final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LocalDate periodStartDate, final LocalDate periodEndDate) { @@ -649,8 +659,6 @@ public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInO interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero(); } else if (isInterestFreeGracePeriod(periodNumber)) { interestForInstallment = interestForInstallment.zero(); - } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { - interestForInstallment = interestForThisInstallmentAfterGrace; } else { interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace); } @@ -680,7 +688,7 @@ private Money calculateTotalFlatInterestDueWithoutGrace(final PaymentPeriodsInOn switch (this.interestMethod) { case FLAT: final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc); - totalInterestDue = this.principal.minus(totalPrincipalAccountedForInterestCalcualtion) + totalInterestDue = this.disbursedPrincipal.minus(totalPrincipalAccountedForInterestCalcualtion) .multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode()); break; @@ -714,7 +722,7 @@ private BigDecimal calculatePeriodsInLoanTerm() { // number of days from 'ideal disbursement' to final date LocalDate loanStartDate = getExpectedDisbursementDate(); - if (getInterestChargedFromDate() != null && loanStartDate.isBefore(getInterestChargedFromLocalDate())) { + if (DateUtils.isBefore(loanStartDate, getInterestChargedFromLocalDate())) { loanStartDate = getInterestChargedFromLocalDate(); } @@ -771,7 +779,7 @@ public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); } - if (!expectedStartDate.isEqual(startDate)) { + if (!DateUtils.isEqual(expectedStartDate, startDate)) { diffDays = Math.toIntExact(ChronoUnit.DAYS.between(startDate, expectedStartDate)); } if (numberOfMonths == 0) { @@ -1181,7 +1189,7 @@ private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(fin } private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator, - final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance, + final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance, final int periodNumber, LocalDate periodStartDate, LocalDate periodEndDate) { Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance, periodStartDate, @@ -1191,30 +1199,30 @@ private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(fina interest = interest.zero(); } - Double fraction = interestCalculationGraceOnRepaymentPeriodFraction; + BigDecimal fraction = interestCalculationGraceOnRepaymentPeriodFraction; if (isInterestFreeGracePeriod(periodNumber)) { interest = interest.zero(); } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { - if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) { + if (interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ZERO) > 0) { interest = interest.zero(); - fraction = fraction - Integer.valueOf(1).doubleValue(); + fraction = fraction.subtract(BigDecimal.ONE); - } else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.parseDouble("0.25") - && interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) { + } else if (interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.valueOf(0.25)) > 0 + && interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ONE) < 0) { final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction); interest = interest.minus(graceOnInterestForRepaymentPeriod); - fraction = Double.valueOf("0"); + fraction = BigDecimal.ZERO; } } return interest; } - private boolean isInterestFreeGracePeriodFromDate(final double interestCalculationGraceOnRepaymentPeriodFraction) { - return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction > Double.parseDouble("0.0"); + private boolean isInterestFreeGracePeriodFromDate(BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) { + return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ZERO) > 0; } private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) { @@ -1287,7 +1295,7 @@ public LoanProductRelatedDetail toLoanProductRelatedDetail() { this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.daysInYearType.getValue(), this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled, this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled, - this.isScheduleExtensionForDownPaymentDisabled); + this.isScheduleExtensionForDownPaymentDisabled, this.loanScheduleType); } public Integer getLoanTermFrequency() { @@ -1318,6 +1326,10 @@ public void setPrincipal(Money principal) { this.principal = principal; } + public void setDisbursedPrincipal(Money disbursedPrincipal) { + this.disbursedPrincipal = disbursedPrincipal; + } + public LocalDate getInterestChargedFromLocalDate() { return this.interestChargedFromDate; } @@ -1366,6 +1378,7 @@ public boolean isMultiDisburseLoan() { return this.multiDisburseLoan; } + @NotNull public Money getMaxOutstandingBalance() { return Money.of(getCurrency(), this.maxOutstandingBalance); } @@ -1438,7 +1451,7 @@ private boolean isFallingInRepaymentPeriod(LocalDate fromDate, LocalDate toDate) int months = getPeriodsBetween(fromDate, toDate); fromDate = fromDate.plusMonths(months); - isSameAsRepaymentPeriod = fromDate.isEqual(toDate); + isSameAsRepaymentPeriod = DateUtils.isEqual(fromDate, toDate); } break; @@ -1772,4 +1785,12 @@ public LocalDate getSubmittedOnDate() { public boolean isScheduleExtensionForDownPaymentDisabled() { return isScheduleExtensionForDownPaymentDisabled; } + + public Integer getInstallmentAmountInMultiplesOf() { + return installmentAmountInMultiplesOf; + } + + public LoanScheduleType getLoanScheduleType() { + return loanScheduleType; + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorFactory.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorFactory.java index 8ce768f210f..46594b09ecc 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorFactory.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorFactory.java @@ -22,5 +22,5 @@ public interface LoanScheduleGeneratorFactory { - LoanScheduleGenerator create(InterestMethod interestMethod); + LoanScheduleGenerator create(LoanScheduleType loanScheduleType, InterestMethod interestMethod); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java index e7e8d92c241..9b36bce9524 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java @@ -20,7 +20,7 @@ import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Collection; +import java.util.List; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.Money; @@ -32,7 +32,7 @@ */ public final class LoanScheduleModel { - private final Collection periods; + private final List periods; private final ApplicationCurrency applicationCurrency; private final int loanTermInDays; private final Money totalPrincipalDisbursed; @@ -44,7 +44,7 @@ public final class LoanScheduleModel { private final BigDecimal totalRepaymentExpected; private final BigDecimal totalOutstanding; - public static LoanScheduleModel from(final Collection periods, final ApplicationCurrency applicationCurrency, + public static LoanScheduleModel from(final List periods, final ApplicationCurrency applicationCurrency, final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { @@ -54,7 +54,7 @@ public static LoanScheduleModel from(final Collection p totalOutstanding); } - public static LoanScheduleModel withOverdueChargeUpdation(final Collection periods, + public static LoanScheduleModel withOverdueChargeUpdation(final List periods, final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) { return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays, @@ -63,7 +63,7 @@ public static LoanScheduleModel withOverdueChargeUpdation(final Collection periods, + public static LoanScheduleModel withLoanScheduleModelPeriods(final List periods, final LoanScheduleModel loanScheduleModel) { return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays, @@ -72,7 +72,7 @@ public static LoanScheduleModel withLoanScheduleModelPeriods(final Collection periods, final ApplicationCurrency applicationCurrency, + private LoanScheduleModel(final List periods, final ApplicationCurrency applicationCurrency, final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { @@ -97,7 +97,7 @@ public LoanScheduleData toData() { final BigDecimal totalCredits = BigDecimal.ZERO; - final Collection periodsData = new ArrayList<>(); + final List periodsData = new ArrayList<>(); for (final LoanScheduleModelPeriod modelPeriod : this.periods) { periodsData.add(modelPeriod.toData()); } @@ -114,7 +114,7 @@ public LoanScheduleData toData() { totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits); } - public Collection getPeriods() { + public List getPeriods() { return this.periods; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleType.java new file mode 100644 index 00000000000..cf6d574b501 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleType.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; + +public enum LoanScheduleType { + CUMULATIVE, PROGRESSIVE +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PaymentPeriodsInOneYearCalculator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PaymentPeriodsInOneYearCalculator.java index 30b08725f95..508c6379f0a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PaymentPeriodsInOneYearCalculator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PaymentPeriodsInOneYearCalculator.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; +import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; @@ -25,7 +27,7 @@ public interface PaymentPeriodsInOneYearCalculator { Integer calculate(PeriodFrequencyType repaymentFrequencyType); - double calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate, - LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaidEvery); + BigDecimal calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate, + LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, MathContext mc); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java index e3cd73f6d16..c58e7cb504c 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java @@ -154,4 +154,9 @@ public interface LoanProductConstants { String REPAYMENT_START_DATE_TYPE = "repaymentStartDateType"; String DISABLE_SCHEDULE_EXTENSION_FOR_DOWN_PAYMENT = "disableScheduleExtensionForDownPayment"; + String ENABLE_INSTALLMENT_LEVEL_DELINQUENCY = "enableInstallmentLevelDelinquency"; + + // loan schedule type + String LOAN_SCHEDULE_TYPE = "loanScheduleType"; + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java index 2d73d2aa6f0..c18c77409d0 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java @@ -62,6 +62,7 @@ import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate; import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException; import org.apache.fineract.portfolio.rate.domain.Rate; @@ -207,6 +208,9 @@ public class LoanProduct extends AbstractPersistableCustom { @JoinColumn(name = "delinquency_bucket_id") private DelinquencyBucket delinquencyBucket; + @Column(name = "enable_installment_level_delinquency", nullable = false) + private boolean enableInstallmentLevelDelinquency = false; + @Column(name = "due_days_for_repayment_event") private Integer dueDaysForRepaymentEvent; @@ -300,6 +304,14 @@ public static LoanProduct assembleFromJson(final Fund fund, final String loanTra final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate"); final ExternalId externalId = ExternalIdFactory.produce(command.stringValueOfParameterNamedAllowingNull("externalId")); + final LoanScheduleType loanScheduleType; + if (command.hasParameter("loanScheduleType")) { + loanScheduleType = LoanScheduleType.valueOf(command.stringValueOfParameterNamed("loanScheduleType")); + } else { + // For backward compatibility + loanScheduleType = LoanScheduleType.CUMULATIVE; + } + final boolean useBorrowerCycle = command .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME); final Set loanProductBorrowerCycleVariations = new HashSet<>(); @@ -403,6 +415,9 @@ public static LoanProduct assembleFromJson(final Fund fund, final String loanTra final boolean disableScheduleExtensionForDownPayment = command .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.DISABLE_SCHEDULE_EXTENSION_FOR_DOWN_PAYMENT); + final boolean enableInstallmentLevelDelinquency = command + .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY); + return new LoanProduct(fund, loanTransactionProcessingStrategy, loanProductPaymentAllocationRules, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, @@ -420,7 +435,8 @@ public static LoanProduct assembleFromJson(final Fund fund, final String loanTra syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment, disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, - enableAutoRepaymentForDownPayment, repaymentStartDateType, disableScheduleExtensionForDownPayment); + enableAutoRepaymentForDownPayment, repaymentStartDateType, disableScheduleExtensionForDownPayment, + enableInstallmentLevelDelinquency, loanScheduleType); } @@ -635,7 +651,8 @@ public LoanProduct(final Fund fund, final String transactionProcessingStrategyCo final Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final RepaymentStartDateType repaymentStartDateType, - final boolean disableScheduleExtensionForDownPayment) { + final boolean disableScheduleExtensionForDownPayment, final boolean enableInstallmentLevelDelinquency, + final LoanScheduleType loanScheduleType) { this.fund = fund; this.transactionProcessingStrategyCode = transactionProcessingStrategyCode; @@ -677,7 +694,7 @@ public LoanProduct(final Fund fund, final String transactionProcessingStrategyCo recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(), isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, - enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment); + enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment, loanScheduleType); this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings(); @@ -731,6 +748,8 @@ public LoanProduct(final Fund fund, final String transactionProcessingStrategyCo this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent; this.repaymentStartDateType = repaymentStartDateType; + this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; + validateLoanProductPreSave(); } @@ -1307,6 +1326,14 @@ public Map update(final JsonCommand command, final AprCalculator this.loanProductRelatedDetail.updateDisableScheduleExtensionForDownPayment(newValue); } + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, + this.isEnableInstallmentLevelDelinquency())) { + final boolean newValue = command + .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY); + actualChanges.put(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, newValue); + this.updateEnableInstallmentLevelDelinquency(newValue); + } + return actualChanges; } @@ -1706,4 +1733,12 @@ public RepaymentStartDateType getRepaymentStartDateType() { return this.repaymentStartDateType == null ? RepaymentStartDateType.INVALID : this.repaymentStartDateType; } + public boolean isEnableInstallmentLevelDelinquency() { + return enableInstallmentLevelDelinquency; + } + + public void updateEnableInstallmentLevelDelinquency(boolean enableInstallmentLevelDelinquency) { + this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 0ac619ded46..186d163d2c9 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; /** @@ -141,6 +142,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "disable_schedule_extension_for_down_payment", nullable = false) private boolean disableScheduleExtensionForDownPayment; + @Column(name = "loan_schedule_type", nullable = false) + @Enumerated(EnumType.STRING) + private LoanScheduleType loanScheduleType; + public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal, final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, @@ -151,7 +156,8 @@ public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currenc final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, - final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment) { + final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment, + final LoanScheduleType loanScheduleType) { return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, @@ -159,7 +165,7 @@ public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currenc recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment, - disableScheduleExtensionForDownPayment); + disableScheduleExtensionForDownPayment, loanScheduleType); } protected LoanProductRelatedDetail() { @@ -176,7 +182,8 @@ public LoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecima final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, - final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment) { + final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment, + final LoanScheduleType loanScheduleType) { this.currency = currency; this.principal = defaultPrincipal; this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod; @@ -207,6 +214,7 @@ public LoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecima this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment; this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment; this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment; + this.loanScheduleType = loanScheduleType; } private Integer defaultToNullIfZero(final Integer value) { @@ -728,4 +736,8 @@ public boolean isDisableScheduleExtensionForDownPayment() { public void updateDisableScheduleExtensionForDownPayment(boolean disableScheduleExtensionForDownPayment) { this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment; } + + public LoanScheduleType getLoanScheduleType() { + return loanScheduleType; + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java index adf967f6c33..85b3d865d68 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java @@ -37,7 +37,8 @@ public enum PaymentAllocationTransactionType { GOODWILL_CREDIT(LoanTransactionType.GOODWILL_CREDIT, "Goodwill credit"), // CHARGE_REFUND(LoanTransactionType.CHARGE_REFUND, "Charge refund"), // CHARGE_ADJUSTMENT(LoanTransactionType.CHARGE_ADJUSTMENT, "Charge adjustment"), // - WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST, "Waive interest");// + WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST, "Waive interest"), CHARGE_PAYMENT(LoanTransactionType.CHARGE_PAYMENT, + "Charge payment");// private final LoanTransactionType loanTransactionType; private final String humanReadableName; diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml index f2a28a6456a..fa3e74c5e3f 100644 --- a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml @@ -30,4 +30,8 @@ + + + + diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1008_add_loan_installment_delinquency_tag.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1008_add_loan_installment_delinquency_tag.xml new file mode 100644 index 00000000000..a7dc10ee709 --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1008_add_loan_installment_delinquency_tag.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1009_refactor_loan_Installment_delinquency_tag.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1009_refactor_loan_Installment_delinquency_tag.xml new file mode 100644 index 00000000000..62df114406b --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1009_refactor_loan_Installment_delinquency_tag.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1010_introduce_loan_schedule_type_configuration.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1010_introduce_loan_schedule_type_configuration.xml new file mode 100644 index 00000000000..9bae4b0dfe2 --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1010_introduce_loan_schedule_type_configuration.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1011_add_delinquency_actions_table.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1011_add_delinquency_actions_table.xml new file mode 100644 index 00000000000..2a86c409318 --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1011_add_delinquency_actions_table.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapperTest.java b/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapperTest.java index bc838faee1f..8971c199d96 100644 --- a/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapperTest.java +++ b/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapperTest.java @@ -32,9 +32,9 @@ public void testExceptionMapper() { Response response = exceptionMapper.toResponse(new LoanIdsHardLockedException(123L)); Assertions.assertEquals(4090, exceptionMapper.errorCode()); - Assertions.assertEquals("{\"developerMessage\":\"Loan is locked by the COB job. Loan ID: 123\"," - + "\"httpStatusCode\":\"Conflict\"," + "\"defaultUserMessage\":\"Loan is locked by the COB job. Loan ID: \\\" + loanId\"," - + "\"userMessageGlobalisationCode\":\"error.msg.loan.locked\"," + "\"errors\":[]}", response.getEntity()); + Assertions.assertEquals("{\"developerMessage\":\"Loan is locked by the COB job. Loan ID: 123\"," + "\"httpStatusCode\":\"409\"," + + "\"defaultUserMessage\":\"Loan is locked by the COB job. Loan ID: 123\"," + + "\"userMessageGlobalisationCode\":\"error.msg.loan.locked\"}", response.getEntity()); Assertions.assertEquals(409, response.getStatus()); Assertions.assertEquals(MediaType.APPLICATION_JSON, response.getMediaType().toString()); } diff --git a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java new file mode 100644 index 00000000000..31c2ca10fa2 --- /dev/null +++ b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java @@ -0,0 +1,154 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.domain; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.charge.domain.Charge; +import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; +import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; +import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest { + + private final SingleLoanChargeRepaymentScheduleProcessingWrapper underTest = new SingleLoanChargeRepaymentScheduleProcessingWrapper(); + private static final MockedStatic MONEY_HELPER = Mockito.mockStatic(MoneyHelper.class); + + private MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(new CurrencyData("USD")); + + private ArgumentCaptor feeChargesDue = ArgumentCaptor.forClass(Money.class); + private ArgumentCaptor feeChargesWaived = ArgumentCaptor.forClass(Money.class); + private ArgumentCaptor feeChargesWrittenOff = ArgumentCaptor.forClass(Money.class); + private ArgumentCaptor penaltyChargesDue = ArgumentCaptor.forClass(Money.class); + private ArgumentCaptor penaltyChargesWaived = ArgumentCaptor.forClass(Money.class); + private ArgumentCaptor penaltyChargesWrittenOff = ArgumentCaptor.forClass(Money.class); + + @BeforeAll + public static void init() { + MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); + } + + @Test + public void testOnePeriodWithFeeCharge() { + LocalDate disbursementDate = LocalDate.of(2023, 01, 1); + ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, disbursementDate))); + + LoanRepaymentScheduleInstallment period = createPeriod(1, LocalDate.of(2023, 01, 1), LocalDate.of(2023, 01, 30)); + LoanCharge loanCharge = createCharge(false); + + underTest.reprocess(currency, disbursementDate, List.of(period), loanCharge); + + verify(period, "10.0", "0.0", "0.0", "0.0", "0.0", "0.0"); + } + + @Test + public void testOnePeriodWithPenaltyCharge() { + LocalDate disbursementDate = LocalDate.of(2023, 01, 1); + ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, disbursementDate))); + + LoanRepaymentScheduleInstallment period = createPeriod(1, LocalDate.of(2023, 01, 1), LocalDate.of(2023, 01, 30)); + LoanCharge loanCharge = createCharge(true); + + underTest.reprocess(currency, disbursementDate, List.of(period), loanCharge); + + verify(period, "0.0", "0.0", "0.0", "10.0", "0.0", "0.0"); + } + + @Test + public void testTwoPeriodsWithPenaltyCharge() { + LocalDate disbursementDate = LocalDate.of(2023, 01, 1); + ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, disbursementDate))); + + LoanRepaymentScheduleInstallment period1 = createPeriod(1, LocalDate.of(2023, 01, 1), LocalDate.of(2023, 01, 31)); + LoanRepaymentScheduleInstallment period2 = createPeriod(1, LocalDate.of(2023, 02, 1), LocalDate.of(2023, 02, 28)); + + LoanCharge loanCharge = createCharge(true); + + underTest.reprocess(currency, disbursementDate, List.of(period1, period2), loanCharge); + + verify(period1, "0.0", "0.0", "0.0", "10.0", "0.0", "0.0"); + verify(period2, "0.0", "0.0", "0.0", "0.0", "0.0", "0.0"); + } + + private void verify(LoanRepaymentScheduleInstallment period, String expectedFeeChargesDue, String expectedFeeChargesWaived, + String expectedFeeChargesWrittenOff, String expectedPenaltyChargesDue, String expectedPenaltyChargesWaived, + String expectedPenaltyChargesWrittenOff) { + + Mockito.verify(period, times(1)).addToChargePortion(feeChargesDue.capture(), feeChargesWaived.capture(), + feeChargesWrittenOff.capture(), penaltyChargesDue.capture(), penaltyChargesWaived.capture(), + penaltyChargesWrittenOff.capture()); + + Assertions.assertTrue(new BigDecimal(expectedFeeChargesDue).compareTo(feeChargesDue.getValue().getAmount()) == 0); + Assertions.assertTrue(new BigDecimal(expectedFeeChargesWaived).compareTo(feeChargesWaived.getValue().getAmount()) == 0); + Assertions.assertTrue(new BigDecimal(expectedFeeChargesWrittenOff).compareTo(feeChargesWrittenOff.getValue().getAmount()) == 0); + Assertions.assertTrue(new BigDecimal(expectedPenaltyChargesDue).compareTo(penaltyChargesDue.getValue().getAmount()) == 0); + Assertions.assertTrue(new BigDecimal(expectedPenaltyChargesWaived).compareTo(penaltyChargesWaived.getValue().getAmount()) == 0); + Assertions.assertTrue( + new BigDecimal(expectedPenaltyChargesWrittenOff).compareTo(penaltyChargesWrittenOff.getValue().getAmount()) == 0); + } + + @NotNull + private static LoanCharge createCharge(boolean penalty) { + Charge charge = mock(Charge.class); + when(charge.getId()).thenReturn(1L); + when(charge.getName()).thenReturn("charge a"); + when(charge.getCurrencyCode()).thenReturn("UDS"); + when(charge.isPenalty()).thenReturn(penalty); + LoanCharge loanCharge = new LoanCharge(null, charge, new BigDecimal(1000), new BigDecimal(10), ChargeTimeType.SPECIFIED_DUE_DATE, + ChargeCalculationType.FLAT, LocalDate.of(2023, 01, 15), ChargePaymentMode.REGULAR, 1, null, null); + return loanCharge; + } + + @NotNull + private LoanRepaymentScheduleInstallment createPeriod(int periodId, LocalDate start, LocalDate end) { + LoanRepaymentScheduleInstallment period = Mockito.mock(LoanRepaymentScheduleInstallment.class); + Mockito.when(period.getInstallmentNumber()).thenReturn(periodId); + Mockito.when(period.getFromDate()).thenReturn(start); + Mockito.when(period.getDueDate()).thenReturn(end); + Money principal = Money.of(currency, new BigDecimal("1000.0")); + Money interest = Money.of(currency, BigDecimal.ZERO); + + Mockito.when(period.getPrincipal(eq(currency))).thenReturn(principal); + Mockito.when(period.getInterestCharged(eq(currency))).thenReturn(interest); + return period; + } + +} diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle index 2212469ff15..c767aa1a388 100644 --- a/fineract-provider/build.gradle +++ b/fineract-provider/build.gradle @@ -244,6 +244,12 @@ bootJar { jib { from { image = 'azul/zulu-openjdk-alpine:17' + platforms { + platform { + architecture = System.getProperty("os.arch").equals("aarch64")?"arm64":"amd64" + os = 'linux' + } + } } to { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingApiResource.java index 9797354d38e..283bd175775 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingApiResource.java @@ -56,13 +56,13 @@ public class AccrualAccountingApiResource { @Operation(summary = "Executes Periodic Accrual Accounting", method = "POST", description = "Mandatory Fields\n" + "\n" + "tillDate\n") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = AccrualAccountingApiResourceSwagger.PostRunaccrualsRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") }) - public String executePeriodicAccrualAccounting(@QueryParam("product") @Parameter(description = "product") final String productParam, - @Parameter(hidden = true) final String jsonRequestBody) { + public String executePeriodicAccrualAccounting(@QueryParam("product") @Parameter(description = "product") final String productParam, + @Parameter(hidden = true) final String jsonRequestBody) { CommandWrapper commandRequest = null; if (productParam == null || CommandParameterUtil.is(productParam, "loans")) { commandRequest = new CommandWrapperBuilder().excuteAccrualAccounting().withJson(jsonRequestBody).build(); - } else if (CommandParameterUtil.is(productParam, "savings")) { + } else if (CommandParameterUtil.is(productParam, "savings")) { commandRequest = new CommandWrapperBuilder().excuteAccrualAccountingForSavings().withJson(jsonRequestBody).build(); } final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformServiceImpl.java index 7856cf65e52..6cd1e50a5d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/service/AccrualAccountingWritePlatformServiceImpl.java @@ -34,9 +34,7 @@ import org.apache.fineract.infrastructure.core.exception.MultiException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class AccrualAccountingWritePlatformServiceImpl implements AccrualAccountingWritePlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/starter/AccountingAccrualConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/starter/AccountingAccrualConfiguration.java new file mode 100644 index 00000000000..1e704cd217f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/starter/AccountingAccrualConfiguration.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.accounting.accrual.starter; + +import org.apache.fineract.accounting.accrual.serialization.AccrualAccountingDataValidator; +import org.apache.fineract.accounting.accrual.service.AccrualAccountingWritePlatformService; +import org.apache.fineract.accounting.accrual.service.AccrualAccountingWritePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AccountingAccrualConfiguration { + + @Bean + @ConditionalOnMissingBean(AccrualAccountingWritePlatformService.class) + public AccrualAccountingWritePlatformService accrualAccountingWritePlatformService( + LoanAccrualPlatformService loanAccrualPlatformService, AccrualAccountingDataValidator accountingDataValidator) { + return new AccrualAccountingWritePlatformServiceImpl(loanAccrualPlatformService, accountingDataValidator); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index 529862f7179..c4c6bf8c335 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -60,6 +60,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.event.business.domain.journalentry.LoanJournalEntryCreatedBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.organisation.office.domain.Office; @@ -78,9 +79,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionEnumData; import org.springframework.dao.DataAccessException; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class AccountingProcessorHelper { @@ -203,7 +202,7 @@ public SavingsDTO populateSavingsDtoFromMap(final Map accounting final Long savingsChargeId = (Long) savingsChargesPaid.get("savingsChargeId"); final boolean isPenalty = (Boolean) savingsChargesPaid.get("isPenalty"); final boolean accrualRecognized = (Boolean) savingsChargesPaid.get("accrualRecognized"); - + final BigDecimal chargeAmountPaid = (BigDecimal) savingsChargesPaid.get("amount"); ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, chargeAmountPaid, savingsChargeId); chargePaymentDTO.setAccrualRecognized(accrualRecognized); @@ -506,12 +505,9 @@ public void createCreditJournalEntryOrReversalForLoan(final Office office, final * @param transactionDate */ public void checkForBranchClosures(final GLClosure latestGLClosure, final LocalDate transactionDate) { - /** - * check if an accounting closure has happened for this branch after the transaction Date - **/ + // check if an accounting closure has happened for this branch after the transaction Date if (latestGLClosure != null) { - if (latestGLClosure.getClosingDate().isAfter(transactionDate) - || latestGLClosure.getClosingDate().compareTo(transactionDate) == 0 ? Boolean.TRUE : Boolean.FALSE) { + if (!DateUtils.isBefore(latestGLClosure.getClosingDate(), transactionDate)) { throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java index 8e337937feb..3f8e59c3c41 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java @@ -162,9 +162,9 @@ else if (savingsTransactionDTO.getTransactionType().isInterestPosting() && savin transactionId, transactionDate, overdraftAmount, isReversal); if (isPositive) { this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, - AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), - AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId, - transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal); + AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, + amount.subtract(overdraftAmount), isReversal); } } } @@ -185,11 +185,12 @@ else if (savingsTransactionDTO.getTransactionType().isAccrual()) { if (feePayments.size() >= 0 || penaltyPayments.size() > 0) { this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, AccrualAccountsForSavings.FEES_RECEIVABLE.getValue(), AccrualAccountsForSavings.INCOME_FROM_FEES.getValue(), - savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); } else { this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, - AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), - savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), + AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount, isReversal); } } } @@ -218,9 +219,8 @@ else if (savingsTransactionDTO.getTransactionType().isFeeDeduction() && savingsT } this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, - accountTypeToBeDebited, AccrualAccountsForSavings.INCOME_FROM_PENALTIES, - savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, - amount.subtract(overdraftAmount), isReversal, penaltyPayments); + accountTypeToBeDebited, AccrualAccountsForSavings.INCOME_FROM_PENALTIES, savingsProductId, paymentTypeId, + savingsId, transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal, penaltyPayments); } } else { this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, @@ -235,9 +235,8 @@ else if (savingsTransactionDTO.getTransactionType().isFeeDeduction() && savingsT } this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, - accountTypeToBeDebited, AccrualAccountsForSavings.INCOME_FROM_FEES, savingsProductId, - paymentTypeId, savingsId, transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal, - feePayments); + accountTypeToBeDebited, AccrualAccountsForSavings.INCOME_FROM_FEES, savingsProductId, paymentTypeId, + savingsId, transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal, feePayments); } } } @@ -251,8 +250,8 @@ else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) { accountTypeToBeCredited = AccrualAccountsForSavings.FEES_RECEIVABLE; } this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, - AccrualAccountsForSavings.SAVINGS_CONTROL, accountTypeToBeCredited, savingsProductId, - paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, penaltyPayments); + AccrualAccountsForSavings.SAVINGS_CONTROL, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount, isReversal, penaltyPayments); } else { final ChargePaymentDTO chargePaymentDTO = feePayments.get(0); AccrualAccountsForSavings accountTypeToBeCredited = AccrualAccountsForSavings.INCOME_FROM_PENALTIES; @@ -260,8 +259,8 @@ else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) { accountTypeToBeCredited = AccrualAccountsForSavings.FEES_RECEIVABLE; } this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, - AccrualAccountsForSavings.SAVINGS_CONTROL, accountTypeToBeCredited, savingsProductId, - paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments); + AccrualAccountsForSavings.SAVINGS_CONTROL, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount, isReversal, feePayments); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java index 8dfa84213a6..88a826c170d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java @@ -65,10 +65,8 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -@Service @RequiredArgsConstructor public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java index 0d7db6d9245..9b669f2cb47 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java @@ -175,8 +175,7 @@ private void updateOrganizationRunningBalance(LocalDate entityDate) { BigDecimal runningBalance = calculateRunningBalance(entryData, runningBalanceMap); params.add(new Object[] { Boolean.TRUE, runningBalance, officeRunningBalance, - platformSecurityContext.authenticatedUser().getId(), DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(), - entryData.getId() }); + platformSecurityContext.authenticatedUser().getId(), DateUtils.getAuditOffsetDateTime(), entryData.getId() }); batchIndex++; if (batchIndex == batchUpdateSize || index == entryDataList.size() - 1) { this.jdbcTemplate.batchUpdate(sql, params); @@ -213,7 +212,7 @@ private void updateRunningBalance(Long officeId, LocalDate entityDate) { for (JournalEntryData entryData : entryDataList) { BigDecimal runningBalance = calculateRunningBalance(entryData, runningBalanceMap); params.add(new Object[] { runningBalance, platformSecurityContext.authenticatedUser().getId(), - DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(), entryData.getId() }); + DateUtils.getAuditOffsetDateTime(), entryData.getId() }); } this.jdbcTemplate.batchUpdate(sql, params); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index 97d09dcbf8a..93b441c1c55 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -81,11 +81,9 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.NonTransientDataAccessException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -@Service @RequiredArgsConstructor @Slf4j public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements JournalEntryWritePlatformService { @@ -314,9 +312,7 @@ public String revertJournalEntry(final List journalEntries, String final LocalDate journalEntriesTransactionDate = journalEntries.get(0).getTransactionDate(); final GLClosure latestGLClosureByBranch = this.glClosureRepository.getLatestGLClosureByBranch(officeId); if (latestGLClosureByBranch != null) { - if (latestGLClosureByBranch.getClosingDate().isAfter(journalEntriesTransactionDate) - || latestGLClosureByBranch.getClosingDate().compareTo(journalEntriesTransactionDate) == 0 ? Boolean.TRUE - : Boolean.FALSE) { + if (!DateUtils.isBefore(latestGLClosureByBranch.getClosingDate(), journalEntriesTransactionDate)) { final String accountName = null; final String accountGLCode = null; throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.ACCOUNTING_CLOSED, @@ -555,24 +551,22 @@ public void revertShareAccountJournalEntries(final ArrayList transactionId } private void validateBusinessRulesForJournalEntries(final JournalEntryCommand command) { - /** check if date of Journal entry is valid ***/ + // check if date of Journal entry is valid final LocalDate transactionDate = command.getTransactionDate(); // shouldn't be in the future - final LocalDate todaysDate = DateUtils.getBusinessLocalDate(); - if (transactionDate.isAfter(todaysDate)) { + if (DateUtils.isDateInTheFuture(transactionDate)) { throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.FUTURE_DATE, transactionDate, null, null); } // shouldn't be before an accounting closure final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId()); if (latestGLClosure != null) { - if (latestGLClosure.getClosingDate().isAfter(transactionDate) - || latestGLClosure.getClosingDate().compareTo(transactionDate) == 0 ? Boolean.TRUE : Boolean.FALSE) { + if (!DateUtils.isBefore(latestGLClosure.getClosingDate(), transactionDate)) { throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } } - /*** check if credits and debits are valid **/ + // check if credits and debits are valid final SingleDebitOrCreditEntryCommand[] credits = command.getCredits(); final SingleDebitOrCreditEntryCommand[] debits = command.getDebits(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java new file mode 100644 index 00000000000..73a08c06404 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.accounting.journalentry.starter; + +import org.apache.fineract.accounting.closure.domain.GLClosureRepository; +import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper; +import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository; +import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService; +import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; +import org.apache.fineract.accounting.journalentry.serialization.JournalEntryCommandFromApiJsonDeserializer; +import org.apache.fineract.accounting.journalentry.service.AccountingProcessorForLoanFactory; +import org.apache.fineract.accounting.journalentry.service.AccountingProcessorForSavingsFactory; +import org.apache.fineract.accounting.journalentry.service.AccountingProcessorForSharesFactory; +import org.apache.fineract.accounting.journalentry.service.AccountingProcessorHelper; +import org.apache.fineract.accounting.journalentry.service.CashBasedAccountingProcessorForClientTransactions; +import org.apache.fineract.accounting.journalentry.service.JournalEntryReadPlatformService; +import org.apache.fineract.accounting.journalentry.service.JournalEntryReadPlatformServiceImpl; +import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; +import org.apache.fineract.accounting.rule.domain.AccountingRuleRepository; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.office.domain.OfficeRepository; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.apache.fineract.organisation.office.domain.OrganisationCurrencyRepositoryWrapper; +import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; +import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; +import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.apache.fineract.portfolio.client.domain.ClientTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class AccountingJournalEntryConfiguration { + + @Bean + @ConditionalOnMissingBean(AccountingProcessorHelper.class) + public AccountingProcessorHelper accountingProcessorHelper(JournalEntryRepository glJournalEntryRepository, + ProductToGLAccountMappingRepository accountMappingRepository, + FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository, GLClosureRepository closureRepository, + GLAccountRepository glAccountRepository, OfficeRepository officeRepository, LoanTransactionRepository loanTransactionRepository, + ClientTransactionRepository clientTransactionRepository, + SavingsAccountTransactionRepository savingsAccountTransactionRepository, + AccountTransfersReadPlatformService accountTransfersReadPlatformService, ChargeRepositoryWrapper chargeRepositoryWrapper, + BusinessEventNotifierService businessEventNotifierService) { + return new AccountingProcessorHelper(glJournalEntryRepository, accountMappingRepository, financialActivityAccountRepository, + closureRepository, glAccountRepository, officeRepository, loanTransactionRepository, clientTransactionRepository, + savingsAccountTransactionRepository, accountTransfersReadPlatformService, chargeRepositoryWrapper, + businessEventNotifierService); + } + + @Bean + @ConditionalOnMissingBean(JournalEntryReadPlatformService.class) + public JournalEntryReadPlatformService journalEntryReadPlatformService(JdbcTemplate jdbcTemplate, + GLAccountReadPlatformService glAccountReadPlatformService, OfficeReadPlatformService officeReadPlatformService, + ColumnValidator columnValidator, FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper, + PaginationHelper paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator) { + return new JournalEntryReadPlatformServiceImpl(jdbcTemplate, glAccountReadPlatformService, officeReadPlatformService, + columnValidator, financialActivityAccountRepositoryWrapper, paginationHelper, sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(JournalEntryWritePlatformService.class) + public JournalEntryWritePlatformService journalEntryWritePlatformService(GLClosureRepository glClosureRepository, + GLAccountRepository glAccountRepository, JournalEntryRepository glJournalEntryRepository, + OfficeRepositoryWrapper officeRepositoryWrapper, AccountingProcessorForLoanFactory accountingProcessorForLoanFactory, + AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory, + AccountingProcessorForSharesFactory accountingProcessorForSharesFactory, AccountingProcessorHelper helper, + JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer, AccountingRuleRepository accountingRuleRepository, + GLAccountReadPlatformService glAccountReadPlatformService, OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository, + PlatformSecurityContext context, PaymentDetailWritePlatformService paymentDetailWritePlatformService, + FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper, + CashBasedAccountingProcessorForClientTransactions accountingProcessorForClientTransactions) { + return new JournalEntryWritePlatformServiceJpaRepositoryImpl(glClosureRepository, glAccountRepository, glJournalEntryRepository, + officeRepositoryWrapper, accountingProcessorForLoanFactory, accountingProcessorForSavingsFactory, + accountingProcessorForSharesFactory, helper, fromApiJsonDeserializer, accountingRuleRepository, + glAccountReadPlatformService, organisationCurrencyRepository, context, paymentDetailWritePlatformService, + financialActivityAccountRepositoryWrapper, accountingProcessorForClientTransactions); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java index 8e21a8db5a7..39818d37c63 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java @@ -38,9 +38,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor @Slf4j public class ProvisioningEntriesReadPlatformServiceImpl implements ProvisioningEntriesReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java index a12c72c0505..3257da64b38 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java @@ -43,6 +43,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; @@ -58,9 +59,7 @@ import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor @Slf4j public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements ProvisioningEntriesWritePlatformService { @@ -107,7 +106,7 @@ private void revertAndAddJournalEntries(ProvisioningEntryData existingEntryData, private void validateForCreateJournalEntry(ProvisioningEntryData existingEntry, ProvisioningEntry requested) { LocalDate existingDate = existingEntry.getCreatedDate(); LocalDate requestedDate = requested.getCreatedDate(); - if (existingDate.isAfter(requestedDate) || existingDate.compareTo(requestedDate) == 0 ? Boolean.TRUE : Boolean.FALSE) { + if (!DateUtils.isBefore(existingDate, requestedDate)) { throw new ProvisioningJournalEntriesCannotbeCreatedException(existingEntry.getCreatedDate(), requestedDate); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/starter/AccountingProvisioningConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/starter/AccountingProvisioningConfiguration.java new file mode 100644 index 00000000000..1e61bd4546b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/starter/AccountingProvisioningConfiguration.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.accounting.provisioning.starter; + +import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository; +import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntryRepository; +import org.apache.fineract.accounting.provisioning.serialization.ProvisioningEntriesDefinitionJsonDeserializer; +import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesReadPlatformService; +import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesReadPlatformServiceImpl; +import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformService; +import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategoryRepository; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaReadPlatformService; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class AccountingProvisioningConfiguration { + + @Bean + @ConditionalOnMissingBean(ProvisioningEntriesReadPlatformService.class) + public ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService(JdbcTemplate jdbcTemplate, + PaginationHelper loanProductProvisioningEntryDataPaginationHelper, PaginationHelper provisioningEntryDataPaginationHelper, + DatabaseSpecificSQLGenerator sqlGenerator) { + return new ProvisioningEntriesReadPlatformServiceImpl(jdbcTemplate, loanProductProvisioningEntryDataPaginationHelper, + provisioningEntryDataPaginationHelper, sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(ProvisioningEntriesWritePlatformService.class) + public ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService( + ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService, + ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService, LoanProductRepository loanProductRepository, + GLAccountRepository glAccountRepository, OfficeRepositoryWrapper officeRepositoryWrapper, + ProvisioningCategoryRepository provisioningCategoryRepository, PlatformSecurityContext platformSecurityContext, + ProvisioningEntryRepository provisioningEntryRepository, JournalEntryWritePlatformService journalEntryWritePlatformService, + ProvisioningEntriesDefinitionJsonDeserializer fromApiJsonDeserializer, FromJsonHelper fromApiJsonHelper) { + return new ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl(provisioningEntriesReadPlatformService, + provisioningCriteriaReadPlatformService, loanProductRepository, glAccountRepository, officeRepositoryWrapper, + provisioningCategoryRepository, platformSecurityContext, provisioningEntryRepository, journalEntryWritePlatformService, + fromApiJsonDeserializer, fromApiJsonHelper) {}; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformServiceImpl.java index 7e03d423b6b..21ecff0d1d0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleReadPlatformServiceImpl.java @@ -41,9 +41,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class AccountingRuleReadPlatformServiceImpl implements AccountingRuleReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformServiceJpaRepositoryImpl.java index 010e0d01b29..f466f804a6a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/service/AccountingRuleWritePlatformServiceJpaRepositoryImpl.java @@ -52,10 +52,8 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.NonTransientDataAccessException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service @RequiredArgsConstructor @Slf4j public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements AccountingRuleWritePlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/starter/AccountingRuleConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/starter/AccountingRuleConfiguration.java new file mode 100644 index 00000000000..d84671aa1f2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/starter/AccountingRuleConfiguration.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.accounting.rule.starter; + +import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper; +import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService; +import org.apache.fineract.accounting.rule.domain.AccountingRuleRepository; +import org.apache.fineract.accounting.rule.domain.AccountingRuleRepositoryWrapper; +import org.apache.fineract.accounting.rule.serialization.AccountingRuleCommandFromApiJsonDeserializer; +import org.apache.fineract.accounting.rule.service.AccountingRuleReadPlatformService; +import org.apache.fineract.accounting.rule.service.AccountingRuleReadPlatformServiceImpl; +import org.apache.fineract.accounting.rule.service.AccountingRuleWritePlatformService; +import org.apache.fineract.accounting.rule.service.AccountingRuleWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class AccountingRuleConfiguration { + + @Bean + @ConditionalOnMissingBean(AccountingRuleReadPlatformService.class) + public AccountingRuleReadPlatformService accountingRuleReadPlatformService(JdbcTemplate jdbcTemplate, + GLAccountReadPlatformService glAccountReadPlatformService) { + return new AccountingRuleReadPlatformServiceImpl(jdbcTemplate, glAccountReadPlatformService); + } + + @Bean + @ConditionalOnMissingBean(AccountingRuleWritePlatformService.class) + public AccountingRuleWritePlatformService accountingRuleWritePlatformService( + AccountingRuleRepositoryWrapper accountingRuleRepositoryWrapper, AccountingRuleRepository accountingRuleRepository, + GLAccountRepositoryWrapper accountRepositoryWrapper, OfficeRepositoryWrapper officeRepositoryWrapper, + AccountingRuleCommandFromApiJsonDeserializer fromApiJsonDeserializer, CodeValueRepository codeValueRepository) { + return new AccountingRuleWritePlatformServiceJpaRepositoryImpl(accountingRuleRepositoryWrapper, accountingRuleRepository, + accountRepositoryWrapper, officeRepositoryWrapper, fromApiJsonDeserializer, codeValueRepository); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java index ffacba20897..45745439573 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java @@ -22,30 +22,22 @@ import java.sql.SQLException; import java.time.ZonedDateTime; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.adhocquery.data.AdHocData; import org.apache.fineract.adhocquery.exception.AdHocNotFoundException; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class AdHocReadPlatformServiceImpl implements AdHocReadPlatformService { private final JdbcTemplate jdbcTemplate; private final DatabaseSpecificSQLGenerator sqlGenerator; private final AdHocMapper adHocRowMapper; - @Autowired - public AdHocReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator) { - this.jdbcTemplate = jdbcTemplate; - this.sqlGenerator = sqlGenerator; - this.adHocRowMapper = new AdHocMapper(sqlGenerator); - } - @Override public Collection retrieveAllAdHocQuery() { final String sql = "select " + this.adHocRowMapper.schema() + " order by r.id"; @@ -70,7 +62,7 @@ public AdHocData retrieveOne(final Long id) { } } - protected static final class AdHocMapper implements RowMapper { + public static final class AdHocMapper implements RowMapper { private final DatabaseSpecificSQLGenerator sqlGenerator; diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java index 5f81ff8f06b..c436a20bbfb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java @@ -32,10 +32,8 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.NonTransientDataAccessException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service @Slf4j @RequiredArgsConstructor public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/starter/AdhocQueryConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/starter/AdhocQueryConfiguration.java new file mode 100644 index 00000000000..f70585d0c54 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/starter/AdhocQueryConfiguration.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.adhocquery.starter; + +import org.apache.fineract.adhocquery.domain.AdHocRepository; +import org.apache.fineract.adhocquery.service.AdHocDataValidator; +import org.apache.fineract.adhocquery.service.AdHocReadPlatformService; +import org.apache.fineract.adhocquery.service.AdHocReadPlatformServiceImpl; +import org.apache.fineract.adhocquery.service.AdHocWritePlatformService; +import org.apache.fineract.adhocquery.service.AdHocWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class AdhocQueryConfiguration { + + @Bean + public AdHocReadPlatformServiceImpl.AdHocMapper adHocRowMapper(DatabaseSpecificSQLGenerator sqlGenerator) { + return new AdHocReadPlatformServiceImpl.AdHocMapper(sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(AdHocReadPlatformService.class) + public AdHocReadPlatformService adHocReadPlatformService(JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator, + AdHocReadPlatformServiceImpl.AdHocMapper adHocRowMapper) { + return new AdHocReadPlatformServiceImpl(jdbcTemplate, sqlGenerator, adHocRowMapper) { + + }; + } + + @Bean + @ConditionalOnMissingBean(AdHocWritePlatformService.class) + public AdHocWritePlatformService adHocWritePlatformService(PlatformSecurityContext context, AdHocRepository adHocRepository, + AdHocDataValidator adHocCommandFromApiJsonDeserializer) { + return new AdHocWritePlatformServiceJpaRepositoryImpl(context, adHocRepository, adHocCommandFromApiJsonDeserializer) { + + }; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionByExternalIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustLoanTransactionByExternalIdCommandStrategy.java similarity index 97% rename from fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionByExternalIdCommandStrategy.java rename to fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustLoanTransactionByExternalIdCommandStrategy.java index 6ae549d7126..e66d59d2d75 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionByExternalIdCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustLoanTransactionByExternalIdCommandStrategy.java @@ -45,7 +45,7 @@ */ @Component @RequiredArgsConstructor -public class AdjustTransactionByExternalIdCommandStrategy implements CommandStrategy { +public class AdjustLoanTransactionByExternalIdCommandStrategy implements CommandStrategy { /** * Loan transactions api resource {@link LoanTransactionsApiResource}. diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustLoanTransactionCommandStrategy.java similarity index 97% rename from fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategy.java rename to fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustLoanTransactionCommandStrategy.java index 61031ee241a..b2e1447c161 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustLoanTransactionCommandStrategy.java @@ -44,7 +44,7 @@ */ @Component @RequiredArgsConstructor -public class AdjustTransactionCommandStrategy implements CommandStrategy { +public class AdjustLoanTransactionCommandStrategy implements CommandStrategy { /** * Loan transactions api resource {@link LoanTransactionsApiResource}. diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplySavingsCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplySavingsCommandStrategy.java index 918e8a8397a..a8676ec4214 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplySavingsCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/ApplySavingsCommandStrategy.java @@ -48,23 +48,11 @@ public class ApplySavingsCommandStrategy implements CommandStrategy { @Override public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) { + // Calls 'submitApplication' function from 'SavingsAccountsApiResource' to Apply Savings to an existing client + String responseBody = savingsAccountsApiResource.submitApplication(request.getBody()); - final BatchResponse response = new BatchResponse(); - final String responseBody; - - response.setRequestId(request.getRequestId()); - response.setHeaders(request.getHeaders()); - - // Calls 'submitApplication' function from - // 'SavingsAccountsApiResource' to Apply Savings to an existing - // client - responseBody = savingsAccountsApiResource.submitApplication(request.getBody()); - - response.setStatusCode(HttpStatus.SC_OK); - // Sets the body of the response after savings is successfully - // applied - response.setBody(responseBody); - - return response; + // Sets the body of the response after savings is successfully applied + return new BatchResponse().setRequestId(request.getRequestId()).setStatusCode(HttpStatus.SC_OK).setBody(responseBody) + .setHeaders(request.getHeaders()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateDatatableEntryCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateDatatableEntryCommandStrategy.java index c6f69a0b43c..b37c7d749b0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateDatatableEntryCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateDatatableEntryCommandStrategy.java @@ -48,27 +48,17 @@ public class CreateDatatableEntryCommandStrategy implements CommandStrategy { @Override public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) { - - final BatchResponse response = new BatchResponse(); - final String responseBody; - - response.setRequestId(request.getRequestId()); - response.setHeaders(request.getHeaders()); - final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); // Pluck out the datatable name & loanId out of the relative path final String datatableName = pathParameters.get(1); final Long loanId = Long.parseLong(pathParameters.get(2)); - // Calls 'createDatatableEntry' function from - // 'DatatablesApiResource' to create a datatable entry on an existing loan - responseBody = datatablesApiResource.createDatatableEntry(datatableName, loanId, request.getBody()); - - response.setStatusCode(HttpStatus.SC_OK); - // Sets the body of the response after datatable entry is successfully - // created - response.setBody(responseBody); + // Calls 'createDatatableEntry' function from 'DatatablesApiResource' to create a datatable entry on an existing + // loan + final String responseBody = datatablesApiResource.createDatatableEntry(datatableName, loanId, request.getBody()); - return response; + // Create the response after datatable entry is successfully created + return new BatchResponse().setRequestId(request.getRequestId()).setStatusCode(HttpStatus.SC_OK).setBody(responseBody) + .setHeaders(request.getHeaders()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DeleteDatatableEntryOneToManyCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DeleteDatatableEntryOneToManyCommandStrategy.java new file mode 100644 index 00000000000..9b0a504b327 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DeleteDatatableEntryOneToManyCommandStrategy.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.batch.command.internal; + +import static org.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.infrastructure.dataqueries.api.DatatablesApiResource; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and deletes a new datatable entry for a given entity. It passes the contents of + * the body from the BatchRequest to {@link DatatablesApiResource} and gets back the response. This class will also + * catch any errors raised by {@link DatatablesApiResource} and map those errors to appropriate status codes in + * BatchResponse. + * + * @see CommandStrategy + * @see BatchRequest + * @see BatchResponse + */ +@Component +@RequiredArgsConstructor +public class DeleteDatatableEntryOneToManyCommandStrategy implements CommandStrategy { + + private final DatatablesApiResource datatablesApiResource; + + @Override + public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + final String responseBody; + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + // Pluck out the datatable name & entityId out of the relative path + final String datatableName = pathParameters.get(1); + final Long entityId = Long.parseLong(pathParameters.get(2)); + final Long entryId = Long.parseLong(pathParameters.get(3)); + + // Calls 'deleteDatatableEntry' function from 'DatatablesApiResource' to delete a datatable entry on an existing + // entity + responseBody = datatablesApiResource.deleteDatatableEntry(datatableName, entityId, entryId); + + response.setStatusCode(HttpStatus.SC_OK); + // Sets the body of the response after datatable entry is successfully deleted created + response.setBody(responseBody); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DeleteDatatableEntryOneToOneCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DeleteDatatableEntryOneToOneCommandStrategy.java new file mode 100644 index 00000000000..8a43fd6bf40 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/DeleteDatatableEntryOneToOneCommandStrategy.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.batch.command.internal; + +import static org.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.infrastructure.dataqueries.api.DatatablesApiResource; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and deletes a new datatable entry for a given entity. It passes the contents of + * the body from the BatchRequest to {@link DatatablesApiResource} and gets back the response. This class will also + * catch any errors raised by {@link DatatablesApiResource} and map those errors to appropriate status codes in + * BatchResponse. + * + * @see CommandStrategy + * @see BatchRequest + * @see BatchResponse + */ +@Component +@RequiredArgsConstructor +public class DeleteDatatableEntryOneToOneCommandStrategy implements CommandStrategy { + + private final DatatablesApiResource datatablesApiResource; + + @Override + public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + final String responseBody; + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + // Pluck out the datatable name & entityId out of the relative path + final String datatableName = pathParameters.get(1); + final Long entityId = Long.parseLong(pathParameters.get(2)); + + // Calls 'deleteDatatableEntry' function from 'DatatablesApiResource' to delete a datatable entry on an existing + // entity + responseBody = datatablesApiResource.deleteDatatableEntries(datatableName, entityId); + + response.setStatusCode(HttpStatus.SC_OK); + // Sets the body of the response after datatable entry is successfully deleted created + response.setBody(responseBody); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByExternalIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanTransactionByExternalIdCommandStrategy.java similarity index 98% rename from fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByExternalIdCommandStrategy.java rename to fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanTransactionByExternalIdCommandStrategy.java index ccb4147324b..0d643198570 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByExternalIdCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanTransactionByExternalIdCommandStrategy.java @@ -48,7 +48,7 @@ */ @Component @RequiredArgsConstructor -public class GetTransactionByExternalIdCommandStrategy implements CommandStrategy { +public class GetLoanTransactionByExternalIdCommandStrategy implements CommandStrategy { /** * Loan transactions api resource {@link LoanTransactionsApiResource}. diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanTransactionByIdCommandStrategy.java similarity index 98% rename from fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategy.java rename to fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanTransactionByIdCommandStrategy.java index 018ce3ff7b7..b31a2c040ce 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanTransactionByIdCommandStrategy.java @@ -47,7 +47,7 @@ */ @Component @RequiredArgsConstructor -public class GetTransactionByIdCommandStrategy implements CommandStrategy { +public class GetLoanTransactionByIdCommandStrategy implements CommandStrategy { /** * Loan transactions api resource {@link LoanTransactionsApiResource}. diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetSavingsAccountByIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetSavingsAccountByIdCommandStrategy.java index a43eafaa56b..7bf56affe1d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetSavingsAccountByIdCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetSavingsAccountByIdCommandStrategy.java @@ -43,14 +43,9 @@ public class GetSavingsAccountByIdCommandStrategy implements CommandStrategy { @Override public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo) { final MutableUriInfo parameterizedUriInfo = new MutableUriInfo(uriInfo); - final BatchResponse response = new BatchResponse(); - - response.setRequestId(batchRequest.getRequestId()); - response.setHeaders(batchRequest.getHeaders()); - final String relativeUrl = relativeUrlWithoutVersion(batchRequest); - final Long savingsAccountId; + final long savingsAccountId; Map queryParameters = new HashMap<>(); if (relativeUrl.indexOf('?') > 0) { savingsAccountId = Long.parseLong(StringUtils.substringBetween(relativeUrl, "/", "?")); @@ -74,10 +69,7 @@ public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo) { final String responseBody = savingsAccountsApiResource.retrieveOne(savingsAccountId, Boolean.parseBoolean(staffInSelectedOfficeOnly), chargeStatus, uriInfo); - response.setStatusCode(HttpStatus.SC_OK); - - response.setBody(responseBody); - - return response; + return new BatchResponse().setRequestId(batchRequest.getRequestId()).setStatusCode(HttpStatus.SC_OK).setBody(responseBody) + .setHeaders(batchRequest.getHeaders()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/SavingsAccountAdjustTransactionCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/SavingsAccountAdjustTransactionCommandStrategy.java index e49d3b4420e..3c62f14a127 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/SavingsAccountAdjustTransactionCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/SavingsAccountAdjustTransactionCommandStrategy.java @@ -42,10 +42,6 @@ public class SavingsAccountAdjustTransactionCommandStrategy implements CommandSt @Override public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo) { - final BatchResponse response = new BatchResponse(); - - response.setRequestId(batchRequest.getRequestId()); - response.setHeaders(batchRequest.getHeaders()); String relativeUrl = relativeUrlWithoutVersion(batchRequest); final List pathParameters; String command = null; @@ -63,10 +59,7 @@ public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo) { final String responseBody = savingsAccountTransactionsApiResource.adjustTransaction(savingsAccountId, transactionId, command, batchRequest.getBody()); - response.setStatusCode(HttpStatus.SC_OK); - - response.setBody(responseBody); - - return response; + return new BatchResponse().setRequestId(batchRequest.getRequestId()).setStatusCode(HttpStatus.SC_OK).setBody(responseBody) + .setHeaders(batchRequest.getHeaders()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/SavingsAccountTransactionCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/SavingsAccountTransactionCommandStrategy.java index 9c2bdb27ffa..5040ba10ff6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/SavingsAccountTransactionCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/SavingsAccountTransactionCommandStrategy.java @@ -41,10 +41,6 @@ public class SavingsAccountTransactionCommandStrategy implements CommandStrategy @Override public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo) { - final BatchResponse response = new BatchResponse(); - - response.setRequestId(batchRequest.getRequestId()); - response.setHeaders(batchRequest.getHeaders()); String relativeUrl = relativeUrlWithoutVersion(batchRequest); final List pathParameters = Splitter.on('/').splitToList(relativeUrl); String command = null; @@ -56,10 +52,7 @@ public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo) { Long savingsAccountId = Long.parseLong(pathParameters.get(1)); final String responseBody = savingsAccountTransactionsApiResource.transaction(savingsAccountId, command, batchRequest.getBody()); - response.setStatusCode(HttpStatus.SC_OK); - - response.setBody(responseBody); - - return response; + return new BatchResponse().setRequestId(batchRequest.getRequestId()).setStatusCode(HttpStatus.SC_OK).setBody(responseBody) + .setHeaders(batchRequest.getHeaders()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLock.java b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLock.java index ce151a2725e..45f77d9b964 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLock.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLock.java @@ -64,7 +64,7 @@ public class LoanAccountLock { public LoanAccountLock(Long loanId, LockOwner lockOwner, LocalDate lockPlacedOnCobBusinessDate) { this.loanId = loanId; this.lockOwner = lockOwner; - this.lockPlacedOn = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); + this.lockPlacedOn = DateUtils.getAuditOffsetDateTime(); this.lockPlacedOnCobBusinessDate = lockPlacedOnCobBusinessDate; } @@ -75,6 +75,6 @@ public void setError(String errorMessage, String stacktrace) { public void setNewLockOwner(LockOwner newLockOwner) { this.lockOwner = newLockOwner; - this.lockPlacedOn = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); + this.lockPlacedOn = DateUtils.getAuditOffsetDateTime(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStep.java index 153a064e4ec..fc815f93741 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStep.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStep.java @@ -18,7 +18,9 @@ */ package org.apache.fineract.cob.loan; +import java.math.BigDecimal; import java.time.LocalDate; +import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,6 +30,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.springframework.stereotype.Component; @Slf4j @@ -51,7 +54,10 @@ public Loan execute(Loan loan) { final List loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); for (LoanRepaymentScheduleInstallment repaymentSchedule : loanRepaymentScheduleInstallments) { LocalDate repaymentDate = repaymentSchedule.getDueDate(); - if (repaymentDate.minusDays(numberOfDaysBeforeDueDateToRaiseEvent).equals(currentDate)) { + List nonDisbursedStatuses = Arrays.asList(LoanStatus.INVALID, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, + LoanStatus.APPROVED); + if (isDueEventNeededToBeSent(loan, numberOfDaysBeforeDueDateToRaiseEvent, currentDate, repaymentSchedule, repaymentDate, + nonDisbursedStatuses)) { businessEventNotifierService.notifyPostBusinessEvent(new LoanRepaymentDueBusinessEvent(repaymentSchedule)); break; } @@ -69,4 +75,12 @@ public String getEnumStyledName() { public String getHumanReadableName() { return "Check loan repayment due"; } + + private static boolean isDueEventNeededToBeSent(Loan loan, Long numberOfDaysBeforeDueDateToRaiseEvent, LocalDate currentDate, + LoanRepaymentScheduleInstallment repaymentScheduleInstallment, LocalDate repaymentDate, List nonDisbursedStatuses) { + return repaymentDate.minusDays(numberOfDaysBeforeDueDateToRaiseEvent).equals(currentDate) + && !nonDisbursedStatuses.contains(loan.getStatus()) + && loan.getLoanSummary().getTotalOutstanding().compareTo(BigDecimal.ZERO) > 0 + && repaymentScheduleInstallment.getTotalOutstanding(loan.getCurrency()).isGreaterThanZero(); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java index e01ffdc8e45..4b405bf2129 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java @@ -81,8 +81,8 @@ private Map getPartitions(int partitionSize, Set loanCOBPartitions = new ArrayList<>(retrieveLoanIdService.retrieveLoanCOBPartitions(numberOfDays, - businessDate, isCatchUp != null ? isCatchUp : false, partitionSize)); + List loanCOBPartitions = new ArrayList<>( + retrieveLoanIdService.retrieveLoanCOBPartitions(numberOfDays, businessDate, isCatchUp != null && isCatchUp, partitionSize)); sw.stop(); // if there is no loan to be closed, we still would like to create at least one partition diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java index 25c661ac252..3ac0a769f1d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java @@ -67,7 +67,7 @@ public void upgradeLock(List accountsToLock, LockOwner lockOwner) { UPDATE m_loan_account_locks SET version= version + 1, lock_owner = ?, lock_placed_on = ? WHERE loan_id = ? """, accountsToLock, getInClauseParameterSizeLimit(), (ps, id) -> { ps.setString(1, lockOwner.name()); - ps.setObject(2, DateUtils.getOffsetDateTimeOfTenantWithMostPrecision()); + ps.setObject(2, DateUtils.getAuditOffsetDateTime()); ps.setLong(3, id); }); } @@ -97,7 +97,7 @@ public void applyLock(List loanIds, LockOwner lockOwner) { ps.setLong(1, loanId); ps.setLong(2, 1); ps.setString(3, lockOwner.name()); - ps.setObject(4, DateUtils.getOffsetDateTimeOfTenantWithMostPrecision()); + ps.setObject(4, DateUtils.getAuditOffsetDateTime()); ps.setObject(5, cobBusinessDate); }); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java index fe53d3fd76a..f8376f9b9e0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java @@ -32,6 +32,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.TaskExecutorConstant; import org.apache.fineract.infrastructure.core.domain.FineractContext; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO; import org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository; @@ -73,7 +74,7 @@ public void executeLoanCOBCatchUpAsync(FineractContext context) { LocalDate oldestCOBProcessedDate = !loanIdAndLastClosedBusinessDate.isEmpty() ? loanIdAndLastClosedBusinessDate.get(0).getLastClosedBusinessDate() : cobBusinessDate; - if (oldestCOBProcessedDate.isBefore(cobBusinessDate)) { + if (DateUtils.isBefore(oldestCOBProcessedDate, cobBusinessDate)) { executeLoanCOBDayByDayUntilCOBBusinessDate(oldestCOBProcessedDate, cobBusinessDate); } } catch (NoSuchJobException e) { @@ -92,7 +93,7 @@ private void executeLoanCOBDayByDayUntilCOBBusinessDate(LocalDate oldestCOBProce Job job = jobLocator.getJob(LoanCOBConstant.JOB_NAME); ScheduledJobDetail scheduledJobDetail = scheduledJobDetailRepository.findByJobName(LoanCOBConstant.JOB_HUMAN_READABLE_NAME); LocalDate executingBusinessDate = oldestCOBProcessedDate.plusDays(1); - while (!executingBusinessDate.isAfter(cobBusinessDate)) { + while (!DateUtils.isAfter(executingBusinessDate, cobBusinessDate)) { JobParameterDTO jobParameterDTO = new JobParameterDTO(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, executingBusinessDate.format(DateTimeFormatter.ISO_DATE)); JobParameterDTO jobParameterCatchUpDTO = new JobParameterDTO(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME, "true"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java index 271e43a8d30..b4683172621 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java @@ -51,6 +51,7 @@ import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException; import org.apache.fineract.infrastructure.core.exception.PlatformRequestBodyItemLimitValidationException; import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO; import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; @@ -112,7 +113,7 @@ public void execute(List loanIds, String jobName) { List loansToBeProcessed = getLoansToBeProcessed(loanIds, cobBusinessDate); LocalDate executingBusinessDate = getOldestCOBBusinessDate(loansToBeProcessed).plusDays(1); if (!loansToBeProcessed.isEmpty()) { - while (!executingBusinessDate.isAfter(cobBusinessDate)) { + while (!DateUtils.isAfter(executingBusinessDate, cobBusinessDate)) { execute(getLoanIdsToBeProcessed(loansToBeProcessed, executingBusinessDate), jobName, executingBusinessDate); executingBusinessDate = executingBusinessDate.plusDays(1); } @@ -123,7 +124,7 @@ private List getLoanIdsToBeProcessed(List List loanIdsToBeProcessed = new ArrayList<>(); loansToBeProcessed.forEach(loan -> { if (loan.getLastClosedBusinessDate() != null) { - if (loan.getLastClosedBusinessDate().isBefore(executingBusinessDate)) { + if (DateUtils.isBefore(loan.getLastClosedBusinessDate(), executingBusinessDate)) { loanIdsToBeProcessed.add(loan.getId()); } } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java index 0485b37f8be..bfad524f3d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java @@ -69,9 +69,7 @@ import org.apache.fineract.useradministration.service.AppUserReadPlatformService; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor @Slf4j public class AuditReadPlatformServiceImpl implements AuditReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/starter/CommandsConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/commands/starter/CommandsConfiguration.java new file mode 100644 index 00000000000..774d148332d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/starter/CommandsConfiguration.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.commands.starter; + +import org.apache.fineract.commands.service.AuditReadPlatformService; +import org.apache.fineract.commands.service.AuditReadPlatformServiceImpl; +import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; +import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; +import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; +import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformService; +import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService; +import org.apache.fineract.useradministration.service.AppUserReadPlatformService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class CommandsConfiguration { + + @Bean + @ConditionalOnMissingBean(AuditReadPlatformService.class) + public AuditReadPlatformService auditReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context, + FromJsonHelper fromApiJsonHelper, AppUserReadPlatformService appUserReadPlatformService, + OfficeReadPlatformService officeReadPlatformService, ClientReadPlatformService clientReadPlatformService, + LoanProductReadPlatformService loanProductReadPlatformService, StaffReadPlatformService staffReadPlatformService, + PaginationHelper paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator, + PaginationParametersDataValidator paginationParametersDataValidator, + SavingsProductReadPlatformService savingsProductReadPlatformService, + DepositProductReadPlatformService depositProductReadPlatformService, ColumnValidator columnValidator) { + return new AuditReadPlatformServiceImpl(jdbcTemplate, context, fromApiJsonHelper, appUserReadPlatformService, + officeReadPlatformService, clientReadPlatformService, loanProductReadPlatformService, staffReadPlatformService, + paginationHelper, sqlGenerator, paginationParametersDataValidator, savingsProductReadPlatformService, + depositProductReadPlatformService, columnValidator); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/domain/ImportDocument.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/domain/ImportDocument.java index b8a00129d93..139802962f4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/domain/ImportDocument.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/domain/ImportDocument.java @@ -73,7 +73,7 @@ public static ImportDocument instance(final Document document, final LocalDateTi final Boolean completed = Boolean.FALSE; final Integer successCount = 0; final Integer failureCount = 0; - final LocalDateTime endTime = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); + final LocalDateTime endTime = DateUtils.getLocalDateTimeOfTenant(); return new ImportDocument(document, importTime, endTime, completed, entityType, createdBy, totalRecords, successCount, failureCount); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java index 5e0604c0607..07c720d205d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java @@ -145,8 +145,7 @@ public static LocalDate readAsDate(int colIndex, Row row) { return null; } - LocalDate localDate = LocalDate.ofInstant(c.getDateCellValue().toInstant(), DateUtils.getDateTimeZoneOfTenant()); - return localDate; + return LocalDate.ofInstant(c.getDateCellValue().toInstant(), DateUtils.getDateTimeZoneOfTenant()); } public static Boolean readAsBoolean(int colIndex, Row row) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/SharedProductsSheetPopulator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/SharedProductsSheetPopulator.java index 5e207236a5c..2807875e762 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/SharedProductsSheetPopulator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/SharedProductsSheetPopulator.java @@ -101,8 +101,7 @@ private BigDecimal deriveMarketPrice(final ShareProductData shareProductData) { if (marketDataSet != null && !marketDataSet.isEmpty()) { LocalDate currentDate = DateUtils.getBusinessLocalDate(); for (ShareProductMarketPriceData data : marketDataSet) { - LocalDate futureDate = data.getFromDate(); - if (currentDate.isAfter(futureDate)) { + if (DateUtils.isBefore(data.getFromDate(), currentDate)) { marketValue = data.getShareValue(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java index 408c3df3bd0..7af4a580d25 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java @@ -160,7 +160,7 @@ public static EmailCampaign instance(final AppUser submittedBy, final Report bus final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); - LocalDateTime recurrenceStartDate = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); + LocalDateTime recurrenceStartDate = DateUtils.getLocalDateTimeOfTenant(); if (EmailCampaignType.fromInt(campaignType.intValue()).isSchedule()) { if (command.hasParameter(EmailCampaignValidator.recurrenceStartDate)) { recurrenceStartDate = LocalDateTime.parse(command.stringValueOfParameterNamed(EmailCampaignValidator.recurrenceStartDate), @@ -178,7 +178,6 @@ public static EmailCampaign instance(final AppUser submittedBy, final Report bus } public Map update(JsonCommand command) { - final Map actualChanges = new LinkedHashMap<>(5); if (command.isChangeInStringParameterNamed(EmailCampaignValidator.campaignName, this.campaignName)) { @@ -229,7 +228,6 @@ public Map update(JsonCommand command) { } public void activate(final AppUser currentUser, final DateTimeFormatter formatter, final LocalDate activationLocalDate) { - if (isActive()) { // handle errors if already activated final String defaultUserMessage = "Cannot activate campaign. Campaign is already active."; @@ -271,14 +269,13 @@ public void close(final AppUser currentUser, final DateTimeFormatter dateTimeFor } public void reactivate(final AppUser currentUser, final DateTimeFormatter dateTimeFormat, final LocalDate reactivateLocalDate) { - if (!isClosed()) { // handle errors if already activated final String defaultUserMessage = "Cannot reactivate campaign. Campaign must be in closed state."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.must.be.closed", defaultUserMessage, EmailCampaignValidator.statusParamName, EmailCampaignStatus.fromInt(this.status).getCode()); - final List dataValidationErrors = new ArrayList(); + final List dataValidationErrors = new ArrayList<>(); dataValidationErrors.add(error); throw new PlatformApiDataValidationException(dataValidationErrors); @@ -352,55 +349,45 @@ private void validateClosureDate() { } private void validateActivationDate(final List dataValidationErrors) { - - if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) { - + if (getSubmittedOnDate() != null && DateUtils.isDateInTheFuture(getSubmittedOnDate())) { final String defaultUserMessage = "submitted date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.in.the.future", defaultUserMessage, EmailCampaignValidator.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - - if (getApprovedOnDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getApprovedOnDate())) { - + if (getApprovedOnDate() != null && DateUtils.isAfter(getSubmittedOnDate(), getApprovedOnDate())) { final String defaultUserMessage = "submitted date cannot be after the activation date"; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.after.activation.date", defaultUserMessage, EmailCampaignValidator.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - - if (getApprovedOnDate() != null && isDateInTheFuture(getApprovedOnDate())) { - + if (DateUtils.isDateInTheFuture(getApprovedOnDate())) { final String defaultUserMessage = "Activation date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.activationDate.in.the.future", defaultUserMessage, EmailCampaignValidator.activationDateParamName, getApprovedOnDate()); dataValidationErrors.add(error); } - } private void validateReactivationDate(final List dataValidationErrors) { - if (getApprovedOnDate() != null && isDateInTheFuture(getApprovedOnDate())) { - + if (DateUtils.isDateInTheFuture(getApprovedOnDate())) { final String defaultUserMessage = "Activation date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.activationDate.in.the.future", defaultUserMessage, EmailCampaignValidator.activationDateParamName, getApprovedOnDate()); dataValidationErrors.add(error); } - if (getApprovedOnDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getApprovedOnDate())) { - + if (getApprovedOnDate() != null && DateUtils.isAfter(getSubmittedOnDate(), getApprovedOnDate())) { final String defaultUserMessage = "submitted date cannot be after the activation date"; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.after.activation.date", defaultUserMessage, EmailCampaignValidator.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) { - + if (DateUtils.isDateInTheFuture(getSubmittedOnDate())) { final String defaultUserMessage = "submitted date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.in.the.future", defaultUserMessage, EmailCampaignValidator.submittedOnDateParamName, this.submittedOnDate); @@ -411,7 +398,7 @@ private void validateReactivationDate(final List dataValidati } private void validateClosureDate(final List dataValidationErrors) { - if (getClosureDate() != null && isDateInTheFuture(getClosureDate())) { + if (getClosureDate() != null && DateUtils.isDateInTheFuture(getClosureDate())) { final String defaultUserMessage = "closure date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.closureDate.in.the.future", defaultUserMessage, EmailCampaignValidator.closureDateParamName, this.closureDate); @@ -419,9 +406,4 @@ private void validateClosureDate(final List dataValidationErr dataValidationErrors.add(error); } } - - private boolean isDateInTheFuture(final LocalDate localDate) { - return localDate.isAfter(DateUtils.getBusinessLocalDate()); - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java index 6b7ca94f010..5759c335aab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java @@ -31,7 +31,6 @@ import java.io.StringWriter; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.util.ArrayList; @@ -56,11 +55,9 @@ import org.apache.fineract.infrastructure.core.api.JsonQuery; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; -import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; import org.apache.fineract.infrastructure.dataqueries.domain.Report; import org.apache.fineract.infrastructure.dataqueries.domain.ReportParameterUsage; @@ -98,7 +95,6 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai @Transactional @Override public CommandProcessingResult create(JsonCommand command) { - final AppUser currentUser = this.context.authenticatedUser(); this.emailCampaignValidator.validateCreate(command.json()); @@ -286,24 +282,21 @@ public CommandProcessingResult activateEmailCampaign(Long campaignId, JsonComman if (emailCampaign.isDirect()) { insertDirectCampaignIntoEmailOutboundTable(emailCampaign.getParamValue(), emailCampaign.getEmailSubject(), emailCampaign.getEmailMessage(), emailCampaign.getCampaignName(), emailCampaign.getId()); - } else { - if (emailCampaign.isSchedule()) { - - /** - * if recurrence start date is in the future calculate next trigger date if not use recurrence start - * date us next trigger date when activating - */ - LocalDateTime nextTriggerDateWithTime; - if (emailCampaign.getRecurrenceStartDate().isBefore(tenantDateTime())) { - nextTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), - emailCampaign.getRecurrenceStartDate(), DateUtils.getLocalDateTimeOfTenant()); - } else { - nextTriggerDateWithTime = emailCampaign.getRecurrenceStartDate(); - } - - emailCampaign.setNextTriggerDate(nextTriggerDateWithTime); - this.emailCampaignRepository.saveAndFlush(emailCampaign); + } else if (emailCampaign.isSchedule()) { + // if recurrence start date is in the past, calculate next trigger date, otherwise use recurrence start + // date as next trigger date when activating + LocalDateTime nextTriggerDateWithTime; + LocalDateTime recurrenceStartDate = emailCampaign.getRecurrenceStartDate(); + LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); + if (DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) { + nextTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), recurrenceStartDate, + tenantDateTime); + } else { + nextTriggerDateWithTime = recurrenceStartDate; } + + emailCampaign.setNextTriggerDate(nextTriggerDateWithTime); + this.emailCampaignRepository.saveAndFlush(emailCampaign); } /* @@ -420,7 +413,6 @@ public PreviewCampaignMessage previewMessage(final JsonQuery query) { @Transactional @Override public CommandProcessingResult reactivateEmailCampaign(final Long campaignId, JsonCommand command) { - this.emailCampaignValidator.validateActivation(command.json()); final AppUser currentUser = this.context.authenticatedUser(); @@ -433,23 +425,18 @@ public CommandProcessingResult reactivateEmailCampaign(final Long campaignId, Js final LocalDate reactivationDate = command.localDateValueOfParameterNamed("activationDate"); emailCampaign.reactivate(currentUser, fmt, reactivationDate); if (emailCampaign.isSchedule()) { - - /** - * if recurrence start date is in the future calculate next trigger date if not use recurrence start date us - * next trigger date when activating - */ + // if recurrence start date is in the past, calculate next trigger date, otherwise use recurrence start date + // as next trigger date when activating LocalDateTime nextTriggerDate = null; - if (emailCampaign.getRecurrenceStartDate().isBefore(tenantDateTime())) { - nextTriggerDate = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), emailCampaign.getRecurrenceStartDate(), - DateUtils.getLocalDateTimeOfTenant()); + LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); + LocalDateTime recurrenceStartDate = emailCampaign.getRecurrenceStartDate(); + if (DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) { + nextTriggerDate = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), recurrenceStartDate, tenantDateTime); } else { - nextTriggerDate = emailCampaign.getRecurrenceStartDate(); + nextTriggerDate = recurrenceStartDate; } - // to get time of tenant - final LocalDateTime getTime = emailCampaign.getRecurrenceStartDate(); - - final String dateString = nextTriggerDate.toString() + " " + getTime.getHour() + ":" + getTime.getMinute() + ":" - + getTime.getSecond(); + final String dateString = nextTriggerDate.toString() + " " + recurrenceStartDate.getHour() + ":" + + recurrenceStartDate.getMinute() + ":" + recurrenceStartDate.getSecond(); final DateTimeFormatter simpleDateFormat = new DateTimeFormatterBuilder().parseCaseInsensitive().parseLenient() .appendPattern("yyyy-MM-dd HH:mm:ss").toFormatter(); final LocalDateTime nextTriggerDateWithTime = LocalDateTime.parse(dateString, simpleDateFormat); @@ -470,17 +457,4 @@ private void handleDataIntegrityIssues(@SuppressWarnings("unused") final JsonCom throw new PlatformDataIntegrityException("error.msg.email.campaign.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } - - private LocalDateTime tenantDateTime() { - LocalDateTime today = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); - final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); - - if (tenant != null) { - final ZoneId zone = ZoneId.of(tenant.getTimezoneId()); - if (zone != null) { - today = LocalDateTime.now(zone); - } - } - return today; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/executereportmailingjobs/ExecuteReportMailingJobsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/executereportmailingjobs/ExecuteReportMailingJobsTasklet.java index df9e1d159ff..6a4e77ba895 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/executereportmailingjobs/ExecuteReportMailingJobsTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/executereportmailingjobs/ExecuteReportMailingJobsTasklet.java @@ -80,7 +80,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon final LocalDateTime localDateTimeOftenant = DateUtils.getLocalDateTimeOfTenant(); final LocalDateTime nextRunDateTime = reportMailingJob.getNextRunDateTime(); - if (nextRunDateTime != null && nextRunDateTime.isBefore(localDateTimeOftenant)) { + if (nextRunDateTime != null && DateUtils.isBefore(nextRunDateTime, localDateTimeOftenant)) { final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = ReportMailingJobEmailAttachmentFileFormat .newInstance(reportMailingJob.getEmailAttachmentFileFormat()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/updateemailoutboundwithcampaignmessage/UpdateEmailOutboundWithCampaignMessageTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/updateemailoutboundwithcampaignmessage/UpdateEmailOutboundWithCampaignMessageTasklet.java index 24c93c97af4..4423454d126 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/updateemailoutboundwithcampaignmessage/UpdateEmailOutboundWithCampaignMessageTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/updateemailoutboundwithcampaignmessage/UpdateEmailOutboundWithCampaignMessageTasklet.java @@ -43,7 +43,6 @@ import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignNotFound; import org.apache.fineract.infrastructure.campaigns.email.service.EmailCampaignReadPlatformService; import org.apache.fineract.infrastructure.campaigns.email.service.EmailCampaignWritePlatformService; -import org.apache.fineract.infrastructure.campaigns.jobs.TenantDateTimeUtil; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.client.domain.Client; @@ -69,11 +68,11 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon .retrieveAllScheduleActiveCampaign(); if (emailCampaignDataCollection != null) { for (EmailCampaignData emailCampaignData : emailCampaignDataCollection) { - LocalDateTime tenantDateNow = TenantDateTimeUtil.tenantDateTime(); + LocalDateTime tenantDateNow = DateUtils.getLocalDateTimeOfTenant(); LocalDateTime nextTriggerDate = emailCampaignData.getNextTriggerDate().toLocalDateTime(); log.debug("tenant time {} trigger time {}", tenantDateNow, nextTriggerDate); - if (nextTriggerDate.isBefore(tenantDateNow)) { + if (DateUtils.isBefore(nextTriggerDate, tenantDateNow)) { insertDirectCampaignIntoEmailOutboundTable(emailCampaignData.getParamValue(), emailCampaignData.getEmailSubject(), emailCampaignData.getEmailMessage(), emailCampaignData.getCampaignName(), emailCampaignData.getId()); updateTriggerDates(emailCampaignData.getId()); @@ -115,13 +114,12 @@ private void updateTriggerDates(Long campaignId) { .orElseThrow(() -> new EmailCampaignNotFound(campaignId)); LocalDateTime nextTriggerDate = emailCampaign.getNextTriggerDate(); emailCampaign.setLastTriggerDate(nextTriggerDate); - LocalDateTime newTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), - emailCampaign.getNextTriggerDate(), nextTriggerDate); - if (newTriggerDateWithTime.isBefore(DateUtils.getLocalDateTimeOfTenant())) { - newTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), emailCampaign.getNextTriggerDate(), - DateUtils.getLocalDateTimeOfTenant()); + LocalDateTime newTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), nextTriggerDate, + nextTriggerDate); + LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); + if (DateUtils.isBefore(newTriggerDateWithTime, tenantDateTime)) { + newTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), nextTriggerDate, tenantDateTime); } - emailCampaign.setNextTriggerDate(newTriggerDateWithTime); emailCampaignRepository.saveAndFlush(emailCampaign); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/updatesmsoutboundwithcampaignmessage/UpdateSmsOutboundWithCampaignMessageTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/updatesmsoutboundwithcampaignmessage/UpdateSmsOutboundWithCampaignMessageTasklet.java index 6e430183dc2..e24a42fb729 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/updatesmsoutboundwithcampaignmessage/UpdateSmsOutboundWithCampaignMessageTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/updatesmsoutboundwithcampaignmessage/UpdateSmsOutboundWithCampaignMessageTasklet.java @@ -23,7 +23,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; -import org.apache.fineract.infrastructure.campaigns.jobs.TenantDateTimeUtil; import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignStatus; import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign; @@ -51,12 +50,12 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon .findByTriggerTypeAndStatus(SmsCampaignTriggerType.SCHEDULE.getValue(), SmsCampaignStatus.ACTIVE.getValue()); if (!CollectionUtils.isEmpty(smsCampaignDataCollection)) { for (SmsCampaign smsCampaign : smsCampaignDataCollection) { - LocalDateTime tenantDateNow = TenantDateTimeUtil.tenantDateTime(); + LocalDateTime tenantDateNow = DateUtils.getLocalDateTimeOfTenant(); LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate(); log.debug("tenant time {} trigger time {} {}", tenantDateNow, nextTriggerDate, JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name()); - if (nextTriggerDate.isBefore(tenantDateNow)) { + if (DateUtils.isBefore(nextTriggerDate, tenantDateNow)) { smsCampaignWritePlatformService.insertDirectCampaignIntoSmsOutboundTable(smsCampaign); updateTriggerDates(smsCampaign.getId()); } @@ -70,11 +69,10 @@ private void updateTriggerDates(Long campaignId) { .orElseThrow(() -> new SmsCampaignNotFound(campaignId)); LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate(); smsCampaign.setLastTriggerDate(nextTriggerDate); - LocalDateTime nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate(), - nextTriggerDate); - if (nextRuntime.isBefore(DateUtils.getLocalDateTimeOfTenant())) { - nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate(), - DateUtils.getLocalDateTimeOfTenant()); + LocalDateTime nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), nextTriggerDate, nextTriggerDate); + LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); + if (DateUtils.isBefore(nextRuntime, tenantDateTime)) { + nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), nextTriggerDate, tenantDateTime); } smsCampaign.setNextTriggerDate(nextRuntime); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java index 0d3e8ec9b88..f230d0bc88a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java @@ -119,7 +119,7 @@ public SmsCampaign() {} private SmsCampaign(final String campaignName, final Integer campaignType, final Integer triggerType, final Report businessRuleId, final Long providerId, final String paramValue, final String message, final LocalDate submittedOnDate, - final AppUser submittedBy, final String recurrence, final LocalDateTime localDateTime, final boolean isNotification) { + final AppUser submittedBy, final String recurrence, final LocalDateTime recurrenceStartDate, final boolean isNotification) { this.campaignName = campaignName; this.campaignType = campaignType; this.triggerType = SmsCampaignTriggerType.fromInt(triggerType).getValue(); @@ -131,18 +131,12 @@ private SmsCampaign(final String campaignName, final Integer campaignType, final this.submittedOnDate = submittedOnDate; this.submittedBy = submittedBy; this.recurrence = recurrence; - LocalDateTime recurrenceStartDate = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); this.isVisible = true; - if (localDateTime != null) { - this.recurrenceStartDate = localDateTime; - } else { - this.recurrenceStartDate = recurrenceStartDate; - } + this.recurrenceStartDate = recurrenceStartDate; this.isNotification = isNotification; } public static SmsCampaign instance(final AppUser submittedBy, final Report report, final JsonCommand command) { - final String campaignName = command.stringValueOfParameterNamed(SmsCampaignValidator.campaignName); final Long campaignType = command.longValueOfParameterNamed(SmsCampaignValidator.campaignType); final Long triggerType = command.longValueOfParameterNamed(SmsCampaignValidator.triggerType); @@ -164,10 +158,11 @@ public static SmsCampaign instance(final AppUser submittedBy, final Report repor } String recurrence = null; - LocalDateTime recurrenceStartDate = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); + LocalDateTime recurrenceStartDate = null; if (SmsCampaignTriggerType.fromInt(triggerType.intValue()).isSchedule()) { final Locale locale = command.extractLocale(); String dateTimeFormat; + recurrenceStartDate = DateUtils.getLocalDateTimeOfTenant(); if (command.hasParameter(SmsCampaignValidator.dateTimeFormat)) { dateTimeFormat = command.stringValueOfParameterNamed(SmsCampaignValidator.dateTimeFormat); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(dateTimeFormat).withLocale(locale); @@ -177,8 +172,6 @@ public static SmsCampaign instance(final AppUser submittedBy, final Report repor } recurrence = constructRecurrence(command); } - } else { - recurrenceStartDate = null; } return new SmsCampaign(campaignName, campaignType.intValue(), triggerType.intValue(), report, providerId, paramValue, message, @@ -186,7 +179,6 @@ public static SmsCampaign instance(final AppUser submittedBy, final Report repor } public Map update(JsonCommand command) { - final Map actualChanges = new LinkedHashMap<>(5); if (command.isChangeInStringParameterNamed(SmsCampaignValidator.campaignName, this.campaignName)) { @@ -255,7 +247,6 @@ public Map update(JsonCommand command) { } public void activate(final AppUser currentUser, final DateTimeFormatter formatter, final LocalDate activationLocalDate) { - if (isActive()) { // handle errors if already activated final String defaultUserMessage = "Cannot activate campaign. Campaign is already active."; @@ -297,7 +288,6 @@ public void close(final AppUser currentUser, final DateTimeFormatter dateTimeFor } public void reactivate(final AppUser currentUser, final DateTimeFormatter dateTimeFormat, final LocalDate reactivateLocalDate) { - if (!isClosed()) { // handle errors if already activated final String defaultUserMessage = "Cannot reactivate campaign. Campaign must be in closed state."; @@ -384,66 +374,55 @@ private void validateClosureDate() { } private void validateActivationDate(final List dataValidationErrors) { - - if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) { - + if (DateUtils.isDateInTheFuture(getSubmittedOnDate())) { final String defaultUserMessage = "submitted date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.in.the.future", defaultUserMessage, SmsCampaignValidator.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - - if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) { - + if (getActivationDate() != null && DateUtils.isAfter(getSubmittedOnDate(), getActivationDate())) { final String defaultUserMessage = "submitted date cannot be after the activation date"; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.after.activation.date", defaultUserMessage, SmsCampaignValidator.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - - if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) { - + if (DateUtils.isDateInTheFuture(getActivationDate())) { final String defaultUserMessage = "Activation date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.activationDate.in.the.future", - defaultUserMessage, SmsCampaignValidator.activationDateParamName, getActivationLocalDate()); + defaultUserMessage, SmsCampaignValidator.activationDateParamName, getActivationDate()); dataValidationErrors.add(error); } - } private void validateReactivationDate(final List dataValidationErrors) { - if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) { - + if (DateUtils.isDateInTheFuture(getActivationDate())) { final String defaultUserMessage = "Activation date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.activationDate.in.the.future", - defaultUserMessage, SmsCampaignValidator.activationDateParamName, getActivationLocalDate()); + defaultUserMessage, SmsCampaignValidator.activationDateParamName, getActivationDate()); dataValidationErrors.add(error); } - if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) { - + if (getActivationDate() != null && DateUtils.isAfter(getSubmittedOnDate(), getActivationDate())) { final String defaultUserMessage = "submitted date cannot be after the activation date"; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.after.activation.date", defaultUserMessage, SmsCampaignValidator.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) { - + if (DateUtils.isDateInTheFuture(getSubmittedOnDate())) { final String defaultUserMessage = "submitted date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.in.the.future", defaultUserMessage, SmsCampaignValidator.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } - } private void validateClosureDate(final List dataValidationErrors) { - if (getClosureDate() != null && isDateInTheFuture(getClosureDate())) { + if (DateUtils.isDateInTheFuture(getClosureDate())) { final String defaultUserMessage = "closure date cannot be in the future."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.closureDate.in.the.future", defaultUserMessage, SmsCampaignValidator.closureDateParamName, this.closureDate); @@ -461,14 +440,10 @@ public LocalDate getClosureDate() { return this.closureDate; } - public LocalDate getActivationLocalDate() { + public LocalDate getActivationDate() { return this.approvedOnDate; } - private boolean isDateInTheFuture(final LocalDate localDate) { - return localDate.isAfter(DateUtils.getBusinessLocalDate()); - } - public Report getBusinessRuleId() { return this.businessRuleId; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java index fb02955dafa..582a4b4d859 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java @@ -365,7 +365,7 @@ private HashMap processSavingsTransactionDataForSms(final Saving smsParams.put("depositAmount", savingsAccountTransaction.getAmount(savingsAccount.getCurrency())); smsParams.put("balance", savingsAccount.getWithdrawableBalance()); smsParams.put("officeId", client.getOffice().getId()); - smsParams.put("transactionDate", savingsAccountTransaction.getTransactionLocalDate().format(dateFormatter)); + smsParams.put("transactionDate", savingsAccountTransaction.getTransactionDate().format(dateFormatter)); smsParams.put("savingsTransactionId", savingsAccountTransaction.getId()); if (client.getStaff() != null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java index 0135ed70d32..64e8aeec326 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java @@ -30,7 +30,6 @@ import java.io.StringWriter; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; @@ -54,12 +53,10 @@ import org.apache.fineract.infrastructure.core.api.JsonQuery; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; -import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; import org.apache.fineract.infrastructure.dataqueries.domain.Report; import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository; @@ -107,16 +104,16 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP @Transactional @Override public CommandProcessingResult create(JsonCommand command) { - final AppUser currentUser = this.context.authenticatedUser(); this.smsCampaignValidator.validateCreate(command.json()); final Long runReportId = command.longValueOfParameterNamed(SmsCampaignValidator.runReportId); Report report = this.reportRepository.findById(runReportId).orElseThrow(() -> new ReportNotFoundException(runReportId)); + LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); SmsCampaign smsCampaign = SmsCampaign.instance(currentUser, report, command); - if (smsCampaign.getRecurrenceStartDate() != null - && smsCampaign.getRecurrenceStartDate().isBefore(DateUtils.getLocalDateTimeOfTenant())) { + LocalDateTime recurrenceStartDate = smsCampaign.getRecurrenceStartDate(); + if (recurrenceStartDate != null && DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) { throw new GeneralPlatformDomainRuleException("error.msg.campaign.recurrenceStartDate.in.the.past", - "Recurrence start date cannot be the past date.", smsCampaign.getRecurrenceStartDate()); + "Recurrence start date cannot be the past date.", recurrenceStartDate); } this.smsCampaignRepository.saveAndFlush(smsCampaign); @@ -380,17 +377,15 @@ public CommandProcessingResult activateSmsCampaign(Long campaignId, JsonCommand if (smsCampaign.isDirect()) { insertDirectCampaignIntoSmsOutboundTable(smsCampaign); } else if (smsCampaign.isSchedule()) { - - /** - * if recurrence start date is in the future calculate next trigger date if not use recurrence start date us - * next trigger date when activating - */ + // if recurrence start date is in the future calculate next trigger date if not use recurrence start date us + // next trigger date when activating LocalDateTime nextTriggerDate = null; - if (smsCampaign.getRecurrenceStartDateTime().isBefore(tenantDateTime())) { - nextTriggerDate = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getRecurrenceStartDate(), - DateUtils.getLocalDateTimeOfTenant()); + LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); + LocalDateTime recurrenceStartDate = smsCampaign.getRecurrenceStartDate(); + if (DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) { + nextTriggerDate = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), recurrenceStartDate, tenantDateTime); } else { - nextTriggerDate = smsCampaign.getRecurrenceStartDate(); + nextTriggerDate = recurrenceStartDate; } smsCampaign.setNextTriggerDate(nextTriggerDate); @@ -517,7 +512,6 @@ public CampaignPreviewData previewMessage(final JsonQuery query) { @Transactional @Override public CommandProcessingResult reactivateSmsCampaign(final Long campaignId, JsonCommand command) { - this.smsCampaignValidator.validateActivation(command.json()); final AppUser currentUser = this.context.authenticatedUser(); @@ -532,21 +526,16 @@ public CommandProcessingResult reactivateSmsCampaign(final Long campaignId, Json if (smsCampaign.isDirect()) { insertDirectCampaignIntoSmsOutboundTable(smsCampaign); } else if (smsCampaign.isSchedule()) { - - /** - * if recurrence start date is in the future calculate next trigger date if not use recurrence start date us - * next trigger date when activating - */ + // if recurrence start date is in the past, calculate next trigger date, otherwise use recurrence start date + // as next trigger date when activating LocalDateTime nextTriggerDate = null; - if (smsCampaign.getRecurrenceStartDateTime().isBefore(tenantDateTime())) { - nextTriggerDate = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getRecurrenceStartDate(), - DateUtils.getLocalDateTimeOfTenant()); + LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); + LocalDateTime recurrenceStartDate = smsCampaign.getRecurrenceStartDate(); + if (DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) { + nextTriggerDate = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), recurrenceStartDate, tenantDateTime); } else { - nextTriggerDate = smsCampaign.getRecurrenceStartDate(); + nextTriggerDate = recurrenceStartDate; } - // to get time of tenant - final LocalDateTime getTime = smsCampaign.getRecurrenceStartDateTime(); - smsCampaign.setNextTriggerDate(nextTriggerDate); } this.smsCampaignRepository.saveAndFlush(smsCampaign); @@ -555,21 +544,7 @@ public CommandProcessingResult reactivateSmsCampaign(final Long campaignId, Json } private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause) { - throw new PlatformDataIntegrityException("error.msg.sms.campaign.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } - - private LocalDateTime tenantDateTime() { - LocalDateTime today = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); - final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); - - if (tenant != null) { - final ZoneId zone = ZoneId.of(tenant.getTimezoneId()); - if (zone != null) { - today = LocalDateTime.now(zone); - } - } - return today; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandler.java index f7d755f5b6b..f8bfff14eb5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandler.java @@ -61,7 +61,7 @@ public CustomAuditingHandler(MappingContext, ? private DateTimeProvider fetchDateTimeProvider(Object bean) { if (bean instanceof AbstractAuditableWithUTCDateTimeCustom) { - return CustomDateTimeProvider.TENANT; + return CustomDateTimeProvider.UTC; } else { return CustomDateTimeProvider.INSTANCE; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProvider.java index 839b18341aa..e2ce5d4354a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProvider.java @@ -18,9 +18,6 @@ */ package org.apache.fineract.infrastructure.core.auditing; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneId; import java.time.temporal.TemporalAccessor; import java.util.Optional; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -29,7 +26,7 @@ public enum CustomDateTimeProvider implements DateTimeProvider { - INSTANCE, TENANT; + INSTANCE, UTC; /* * (non-Javadoc) @@ -42,10 +39,10 @@ public Optional getNow() { switch (this) { case INSTANCE -> { - return Optional.of(LocalDateTime.now(ZoneId.systemDefault())); + return Optional.of(DateUtils.getLocalDateTimeOfSystem()); } - case TENANT -> { - return Optional.of(OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant())); + case UTC -> { + return Optional.of(DateUtils.getAuditOffsetDateTime()); } } throw new UnsupportedOperationException(this + " is not supported!"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditorAwareImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditorAwareImpl.java index 46ef34de1ff..981dd8aa9df 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditorAwareImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditorAwareImpl.java @@ -18,10 +18,10 @@ */ package org.apache.fineract.infrastructure.core.domain; +import static org.apache.fineract.useradministration.service.AppUserConstants.ADMIN_USER_ID; + import java.util.Optional; import org.apache.fineract.useradministration.domain.AppUser; -import org.apache.fineract.useradministration.domain.AppUserRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.AuditorAware; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -29,9 +29,6 @@ public class AuditorAwareImpl implements AuditorAware { - @Autowired - private AppUserRepository userRepository; - @Override public Optional getCurrentAuditor() { Optional currentUserId; @@ -50,6 +47,6 @@ public Optional getCurrentAuditor() { } private Optional retrieveSuperUser() { - return Optional.of(1L); + return Optional.of(ADMIN_USER_ID); // TODO change to SYSTEM_USER_ID and add rights } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java index 0d2d96da58b..4e300bda4ca 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java @@ -23,6 +23,7 @@ import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.DATETIME; import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.DECIMAL; import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.INTEGER; +import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.JSON; import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.TEXT; import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.TIMESTAMP; import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.VARCHAR; @@ -40,6 +41,7 @@ import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_DATETIME; import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_DECIMAL; import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_DROPDOWN; +import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_JSON; import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_NUMBER; import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_STRING; import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_TEXT; @@ -97,7 +99,8 @@ public class DatatableCommandFromApiJsonDeserializer { API_FIELD_MANDATORY, API_FIELD_AFTER, API_FIELD_CODE, API_FIELD_NEWCODE, API_FIELD_UNIQUE, API_FIELD_INDEXED); private static final Set SUPPORTED_PARAMETERS_FOR_DROP_COLUMNS = Set.of(API_FIELD_NAME); private static final Object[] SUPPORTED_COLUMN_TYPES = { API_FIELD_TYPE_STRING, API_FIELD_TYPE_NUMBER, API_FIELD_TYPE_BOOLEAN, - API_FIELD_TYPE_DECIMAL, API_FIELD_TYPE_DATE, API_FIELD_TYPE_DATETIME, API_FIELD_TYPE_TEXT, API_FIELD_TYPE_DROPDOWN }; + API_FIELD_TYPE_DECIMAL, API_FIELD_TYPE_DATE, API_FIELD_TYPE_DATETIME, API_FIELD_TYPE_TEXT, API_FIELD_TYPE_JSON, + API_FIELD_TYPE_DROPDOWN }; private final FromJsonHelper fromApiJsonHelper; private final DatabaseTypeResolver databaseTypeResolver; @@ -344,6 +347,8 @@ private static JdbcJavaType mapApiTypeToJdbcType(@NotNull String apiType, boolea return TIMESTAMP; case API_FIELD_TYPE_TEXT: return TEXT; + case API_FIELD_TYPE_JSON: + return JSON; default: { if (fail) { throw new PlatformDataIntegrityException("error.msg.datatable.column.type.invalid", diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java index 2a157c90515..888a8d0a6d7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java @@ -420,7 +420,7 @@ public CreditBureauToken createToken(Long bureauID) { LocalDate current = DateUtils.getLocalDateOfTenant(); LocalDate getExpiryDate = creditBureauToken.getExpires(); - if (getExpiryDate.isBefore(current)) { + if (DateUtils.isBefore(getExpiryDate, current)) { this.tokenRepositoryWrapper.delete(creditBureauToken); creditBureauToken = null; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java index 0028c207088..85bb7fb8e1d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java @@ -85,5 +85,6 @@ private DataTableApiConstant() { public static final String API_FIELD_TYPE_DATETIME = "datetime"; public static final String API_FIELD_TYPE_TIMESTAMP = "timestamp"; public static final String API_FIELD_TYPE_TEXT = "text"; + public static final String API_FIELD_TYPE_JSON = "json"; public static final String API_FIELD_TYPE_DROPDOWN = "dropdown"; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java index 38ddae0d4d2..d0ba53fc6e8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java @@ -185,11 +185,8 @@ public String registerDatatable(@PathParam("datatable") @Parameter(description = @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DatatablesApiResourceSwagger.PutDataTablesResponse.class))) }) public String deregisterDatatable(@PathParam("datatable") @Parameter(description = "datatable") final String datatable) { - this.readWriteNonCoreDataService.deregisterDatatable(datatable); - final CommandProcessingResult result = new CommandProcessingResultBuilder().withResourceIdAsString(datatable).build(); - return this.toApiJsonSerializer.serialize(result); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/EntityDatatableChecksRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/EntityDatatableChecksRepository.java index fa92b24bdc9..eec7e0eff9d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/EntityDatatableChecksRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/EntityDatatableChecksRepository.java @@ -37,7 +37,8 @@ public interface EntityDatatableChecksRepository AND dt.status = :status AND rdt.subtype = :subtype """) - List findByEntityAndStatusAndSubtype(String entity, Integer status, String subtype); + List findByEntityAndStatusAndSubtype(@Param("entity") String entity, @Param("status") Integer status, + @Param("subtype") String subtype); @Query("select t from EntityDatatableChecks t WHERE t.status =:status and t.entity=:entity and t.productId = :productId ") List findByEntityStatusAndProduct(@Param("entity") String entity, @Param("status") Long status, diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteDatatableCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteDatatableCommandHandler.java index c23f24674e6..176c0bf57d3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteDatatableCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteDatatableCommandHandler.java @@ -40,11 +40,8 @@ public DeleteDatatableCommandHandler(final ReadWriteNonCoreDataService writePlat @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - final String datatableName = command.getUrl().replaceAll("/datatables/", ""); - this.writePlatformService.deleteDatatable(datatableName); - return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java index 50722954ccc..9e58f044f4d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java @@ -117,7 +117,7 @@ public List fillResultsetColumnHeaders(final String t // primary keys and unique constrained columns are automatically indexed final boolean columnIsIndexed = columnIsPrimaryKey || columnIsUnique || isExplicitlyIndexed(tableName, columnName, indexDefinitions); - JdbcJavaType jdbcType = JdbcJavaType.getByTypeName(dialect, columnType); + JdbcJavaType jdbcType = JdbcJavaType.getByTypeName(dialect, columnType, false); List columnValues = new ArrayList<>(); String codeName = null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index a5ccb933186..205fdfef7b7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -459,20 +459,16 @@ public String getTableName(String url) { @Transactional @Override public void deregisterDatatable(final String datatable) { - String validatedDatatable = this.preventSqlInjectionService.encodeSql(datatable); - final String permissionList = "('CREATE_" + validatedDatatable + "', 'CREATE_" + validatedDatatable + "_CHECKER', 'READ_" - + validatedDatatable + "', 'UPDATE_" + validatedDatatable + "', 'UPDATE_" + validatedDatatable + "_CHECKER', 'DELETE_" - + validatedDatatable + "', 'DELETE_" + validatedDatatable + "_CHECKER')"; + validateDatatableRegistered(datatable); + final String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_" + + datatable + "', 'UPDATE_" + datatable + "_CHECKER', 'DELETE_" + datatable + "', 'DELETE_" + datatable + "_CHECKER')"; final String deleteRolePermissionsSql = "delete from m_role_permission where m_role_permission.permission_id in (select id from m_permission where code in " + permissionList + ")"; final String deletePermissionsSql = "delete from m_permission where code in " + permissionList; - - final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + validatedDatatable - + "'"; - - final String deleteFromConfigurationSql = "delete from c_configuration where name ='" + validatedDatatable + "'"; + final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'"; + final String deleteFromConfigurationSql = "delete from c_configuration where name ='" + datatable + "'"; String[] sqlArray = new String[4]; sqlArray[0] = deleteRolePermissionsSql; @@ -1250,7 +1246,7 @@ private void dropIndex(String datatableName, String uniqueIndexName) { public void deleteDatatable(final String datatableName) { try { this.context.authenticatedUser(); - validateDatatableRegistered(datatableName); + validateDatatableName(datatableName); assertDataTableEmpty(datatableName); deregisterDatatable(datatableName); String[] sqlArray; @@ -1327,7 +1323,6 @@ private CommandProcessingResult createNewDatatableEntry(final String dataTableNa params.add(SearchUtil.parseJdbcColumnValue(columnHeader, dataParams.get(key), dateFormat, dateTimeFormat, locale, false, sqlGenerator)); } - final String sql = sqlGenerator.buildInsert(dataTableName, insertColumns); if (addScore) { List scoreIds = params.stream().filter(e -> e != null && !String.valueOf(e).isBlank()).toList(); int scoreValue; @@ -1343,6 +1338,8 @@ private CommandProcessingResult createNewDatatableEntry(final String dataTableNa insertColumns.add("score"); params.add(scoreValue); } + + final String sql = sqlGenerator.buildInsert(dataTableName, insertColumns, headersByName); try { int updated = jdbcTemplate.update(sql, params.toArray(Object[]::new)); if (updated != 1) { @@ -1450,8 +1447,8 @@ private CommandProcessingResult updateDatatableEntry(final String dataTableName, Locale locale = localeString == null ? null : JsonParserHelper.localeFromString(localeString); DatabaseType dialect = sqlGenerator.getDialect(); - List updateColumns = new ArrayList<>(List.of(UPDATEDAT_FIELD_NAME)); - List params = new ArrayList<>(List.of(DateUtils.getAuditLocalDateTime())); + ArrayList updateColumns = new ArrayList<>(List.of(UPDATEDAT_FIELD_NAME)); + ArrayList params = new ArrayList<>(List.of(DateUtils.getAuditLocalDateTime())); final HashMap changes = new HashMap<>(); for (String key : dataParams.keySet()) { if (isTechnicalParam(key)) { @@ -1478,7 +1475,8 @@ private CommandProcessingResult updateDatatableEntry(final String dataTableName, if (!updateColumns.isEmpty()) { ResultsetColumnHeaderData pkColumn = SearchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey); params.add(primaryKey); - final String sql = sqlGenerator.buildUpdate(dataTableName, updateColumns) + " WHERE " + pkColumn.getColumnName() + " = ?"; + final String sql = sqlGenerator.buildUpdate(dataTableName, updateColumns, headersByName) + " WHERE " + pkColumn.getColumnName() + + " = ?"; int updated = jdbcTemplate.update(sql, params.toArray(Object[]::new)); if (updated != 1) { throw new PlatformDataIntegrityException("error.msg.invalid.update", "Expected one updated row."); @@ -1587,13 +1585,13 @@ private CommandProcessingResult checkMainResourceExistsWithinScope(@NotNull Enti if (!rs.next()) { throw new DatatableNotFoundException(entityTable, appTableId); } - final Long officeId = rs.getLong("officeId"); - final Long groupId = rs.getLong("groupId"); - final Long clientId = rs.getLong("clientId"); - final Long savingsId = rs.getLong("savingsId"); - final Long loanId = rs.getLong("loanId"); - final Long transactionId = rs.getLong("transactionId"); - final Long entityId = rs.getLong("entityId"); + final Long officeId = (Long) rs.getObject("officeId"); + final Long groupId = (Long) rs.getObject("groupId"); + final Long clientId = (Long) rs.getObject("clientId"); + final Long savingsId = (Long) rs.getObject("savingsId"); + final Long loanId = (Long) rs.getObject("loanId"); + final Long transactionId = (Long) rs.getObject("transactionId"); + final Long entityId = (Long) rs.getObject("entityId"); if (rs.next()) { throw new DatatableSystemErrorException("System Error: More than one row returned from data scoping query"); @@ -1604,7 +1602,7 @@ private CommandProcessingResult checkMainResourceExistsWithinScope(@NotNull Enti .withGroupId(groupId) // .withClientId(clientId) // .withSavingsId(savingsId) // - .withLoanId(loanId).withTransactionId(String.valueOf(transactionId)).withEntityId(entityId)// + .withLoanId(loanId).withTransactionId(transactionId == null ? null : String.valueOf(transactionId)).withEntityId(entityId)// .build(); } @@ -1657,7 +1655,7 @@ private String getClientOfficeJoinCondition(String officeHierarchy, String appTa } private String getGroupOfficeJoinCondition(String officeHierarchy, String appTableAlias) { - return " join m_group g on g.id = " + appTableAlias + ".client_id " + getOfficeJoinCondition(officeHierarchy, "g"); + return " join m_group g on g.id = " + appTableAlias + ".group_id " + getOfficeJoinCondition(officeHierarchy, "g"); } private String getOfficeJoinCondition(String officeHierarchy, String joinTableAlias) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java index fe1f01bee8a..6b1c9c288cb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java @@ -126,7 +126,7 @@ public String createDocument(@PathParam("entityType") @Parameter(description = " final DocumentCommand documentCommand = new DocumentCommand(null, null, entityType, entityId, name, fileDetails.getFileName(), fileSize, bodyPart.getMediaType().toString(), description, null); final Long documentId = this.documentWritePlatformService.createDocument(documentCommand, inputStream); - return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(documentId, null)); + return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(documentId)); } @PUT diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java index 2e82510ca85..9c8b179c1dd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java @@ -201,7 +201,7 @@ public String updateClientImage(@PathParam("entity") final String entityName, @P public String deleteClientImage(@PathParam("entity") final String entityName, @PathParam("entityId") final Long entityId) { validateEntityTypeforImage(entityName); this.imageWritePlatformService.deleteImage(entityName, entityId); - return this.toApiJsonSerializer.serialize(new CommandProcessingResult(entityId)); + return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(entityId)); } /*** Entities for document Management **/ diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformServiceJpaRepositoryImpl.java index b84ddf7144e..2ceb1df734d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/DocumentWritePlatformServiceJpaRepositoryImpl.java @@ -133,7 +133,7 @@ public CommandProcessingResult updateDocument(final DocumentCommand documentComm this.documentRepository.saveAndFlush(documentForUpdate); - return new CommandProcessingResult(documentForUpdate.getId()); + return CommandProcessingResult.resourceResult(documentForUpdate.getId()); } catch (final JpaSystemException | DataIntegrityViolationException dve) { LOG.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.document.unknown.data.integrity.issue", @@ -158,7 +158,7 @@ public CommandProcessingResult deleteDocument(final DocumentCommand documentComm final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository(document.storageType()); contentRepository.deleteFile(document.getLocation()); - return new CommandProcessingResult(document.getId()); + return CommandProcessingResult.resourceResult(document.getId()); } private void validateParentEntityType(final DocumentCommand documentCommand) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java index 04c316e7a2c..21035ca3c66 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java @@ -103,7 +103,7 @@ public CommandProcessingResult deleteImage(String entityName, final Long clientI this.imageRepository.delete(image); } - return new CommandProcessingResult(clientId); + return CommandProcessingResult.resourceResult(clientId); } /** @@ -151,7 +151,7 @@ private CommandProcessingResult updateImage(final Object owner, final String ima } this.imageRepository.save(image); - return new CommandProcessingResult(clientId); + return CommandProcessingResult.resourceResult(clientId); } private Image createImage(Image image, final String imageLocation, final StorageType storageType) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMapping.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMapping.java index a3db3ef7679..4ed6d7e61e1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMapping.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/domain/FineractEntityToEntityMapping.java @@ -33,6 +33,7 @@ import lombok.experimental.Accessors; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.entityaccess.api.FineractEntityApiResourceConstants; import org.apache.fineract.infrastructure.entityaccess.exception.FineractEntityToEntityMappingDateException; @@ -69,7 +70,6 @@ public static FineractEntityToEntityMapping newMap(FineractEntityRelation relati } public Map updateMap(final JsonCommand command) { - final Map actualChanges = new LinkedHashMap<>(9); if (command.isChangeInLongParameterNamed(FineractEntityApiResourceConstants.fromEnityType, this.fromId)) { @@ -95,10 +95,8 @@ public Map updateMap(final JsonCommand command) { actualChanges.put(FineractEntityApiResourceConstants.endDate, valueAsInput); this.endDate = command.localDateValueOfParameterNamed(FineractEntityApiResourceConstants.endDate); } - if (startDate != null && endDate != null) { - if (endDate.isBefore(startDate)) { - throw new FineractEntityToEntityMappingDateException(startDate.toString(), endDate.toString()); - } + if (endDate != null && DateUtils.isBefore(endDate, startDate)) { + throw new FineractEntityToEntityMappingDateException(startDate.toString(), endDate.toString()); } return actualChanges; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteServiceImpl.java index 75aced5d2d8..f83009e3880 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessWriteServiceImpl.java @@ -27,6 +27,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.entityaccess.api.FineractEntityApiResourceConstants; import org.apache.fineract.infrastructure.entityaccess.data.FineractEntityDataValidator; import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccess; @@ -86,9 +87,7 @@ public void addNewEntityAccess(final String entityType, final Long entityId, fin @Override @Transactional public CommandProcessingResult createEntityToEntityMapping(Long relId, JsonCommand command) { - try { - this.fromApiJsonDeserializer.validateForCreate(command.json()); final FineractEntityRelation mapId = this.fineractEntityRelationRepositoryWrapper.findOneWithNotFoundDetection(relId); @@ -99,10 +98,8 @@ public CommandProcessingResult createEntityToEntityMapping(Long relId, JsonComma final LocalDate endDate = command.localDateValueOfParameterNamed(FineractEntityApiResourceConstants.endDate); fromApiJsonDeserializer.checkForEntity(relId.toString(), fromId, toId); - if (startDate != null && endDate != null) { - if (endDate.isBefore(startDate)) { - throw new FineractEntityToEntityMappingDateException(startDate.toString(), endDate.toString()); - } + if (endDate != null && DateUtils.isBefore(endDate, startDate)) { + throw new FineractEntityToEntityMappingDateException(startDate.toString(), endDate.toString()); } final FineractEntityToEntityMapping newMap = FineractEntityToEntityMapping.newMap(mapId, fromId, toId, startDate, endDate); @@ -123,9 +120,7 @@ public CommandProcessingResult createEntityToEntityMapping(Long relId, JsonComma @Override @Transactional public CommandProcessingResult updateEntityToEntityMapping(Long mapId, JsonCommand command) { - try { - this.fromApiJsonDeserializer.validateForUpdate(command.json()); final FineractEntityToEntityMapping mapForUpdate = this.fineractEntityToEntityMappingRepositoryWrapper diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/config/KafkaExternalEventTopicConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/config/KafkaExternalEventTopicConfig.java index 0d5e4562d35..72e4cc7d8fb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/config/KafkaExternalEventTopicConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/config/KafkaExternalEventTopicConfig.java @@ -42,7 +42,7 @@ public class KafkaExternalEventTopicConfig { public KafkaAdmin admin() { Map props = new HashMap<>( fineractProperties.getEvents().getExternal().getProducer().getKafka().getAdmin().getExtraPropertiesMap()); - props.put(BOOTSTRAP_SERVERS_CONFIG, fineractProperties.getRemoteJobMessageHandler().getKafka().getBootstrapServers()); + props.put(BOOTSTRAP_SERVERS_CONFIG, fineractProperties.getEvents().getExternal().getProducer().getKafka().getBootstrapServers()); return new KafkaAdmin(props); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/updatenpa/UpdateNpaTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/updatenpa/UpdateNpaTasklet.java index 08f22b158ef..55b664e4862 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/updatenpa/UpdateNpaTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/updatenpa/UpdateNpaTasklet.java @@ -63,7 +63,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon resetNPASqlBuilder.append("set is_npa = false").append(", last_modified_by = ?, last_modified_on_utc = ? ").append(" FROM ") .append(fromPart).append(wherePart); } - jdbcTemplate.update(resetNPASqlBuilder.toString(), user.getId(), DateUtils.getOffsetDateTimeOfTenantWithMostPrecision()); + jdbcTemplate.update(resetNPASqlBuilder.toString(), user.getId(), DateUtils.getAuditOffsetDateTime()); final StringBuilder updateSqlBuilder = new StringBuilder(900); @@ -82,8 +82,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon .append(fromPart).append(wherePart); } - final int result = jdbcTemplate.update(updateSqlBuilder.toString(), user.getId(), - DateUtils.getOffsetDateTimeOfTenantWithMostPrecision()); + final int result = jdbcTemplate.update(updateSqlBuilder.toString(), user.getId(), DateUtils.getAuditOffsetDateTime()); log.debug("{}: Records affected by updateNPA: {}", ThreadLocalContextUtil.getTenant().getName(), result); return RepeatStatus.FINISHED; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java index 5290c93d23c..5564fcbeb88 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java @@ -67,19 +67,12 @@ public static TFAccessToken create(String token, AppUser user, int tokenLiveTime } public boolean isValid() { - return this.enabled && isDateInTheFuture(getValidTo()) && isDateInThePast(getValidFrom()); + // valid_from is in the past inclusive, valid_to is in the future exclusive + return this.enabled && !DateUtils.isAfterTenantDateTime(getValidFrom()) && DateUtils.isAfterTenantDateTime(getValidTo()); } public AccessTokenData toTokenData() { return new AccessTokenData().setToken(this.token).setValidFrom(getValidFrom().atZone(DateUtils.getDateTimeZoneOfTenant())) .setValidTo(getValidTo().atZone(DateUtils.getDateTimeZoneOfTenant())); } - - private boolean isDateInTheFuture(LocalDateTime dateTime) { - return dateTime.isAfter(DateUtils.getLocalDateTimeOfTenant()); - } - - private boolean isDateInThePast(LocalDateTime dateTime) { - return (dateTime.isBefore(DateUtils.getLocalDateTimeOfTenant()) || dateTime.isEqual(DateUtils.getLocalDateTimeOfTenant())); - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java index 2c44e11dad0..4a8628d3241 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java @@ -157,7 +157,7 @@ public boolean doesPasswordHasToBeRenewed(AppUser currentUser) { final LocalDate passwordExpirationDate = passWordLastUpdateDate.plusDays(passwordDurationDays); - if (DateUtils.getLocalDateOfTenant().isAfter(passwordExpirationDate)) { + if (DateUtils.isBeforeTenantDate(passwordExpirationDate)) { return true; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropAccountData.java index a22cf3dafda..8ce3d5309e5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropAccountData.java @@ -113,7 +113,7 @@ public static InteropAccountData build(SavingsAccount account) { return new InteropAccountData(account.getExternalId().getValue(), product.getId().toString(), product.getName(), product.getShortName(), account.getCurrency().getCode(), account.getAccountBalance(), account.getWithdrawableBalance(), - account.getStatus(), subStatus, account.getAccountType(), account.depositAccountType(), account.getActivationLocalDate(), + account.getStatus(), subStatus, account.getAccountType(), account.depositAccountType(), account.getActivationDate(), calcStatusUpdateOn(account), account.getWithdrawnOnDate(), account.retrieveLastTransactionDate(), ids, account.getClient().getId()); } @@ -125,8 +125,8 @@ private static LocalDate calcStatusUpdateOn(@NotNull SavingsAccount account) { if (account.getWithdrawnOnDate() != null) { return account.getWithdrawnOnDate(); } - if (account.getActivationLocalDate() != null) { - return account.getActivationLocalDate(); + if (account.getActivationDate() != null) { + return account.getActivationDate(); } if (account.getRejectedOnDate() != null) { return account.getRejectedOnDate(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionData.java index dd1106b6f91..64d45ef44f4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionData.java @@ -112,8 +112,8 @@ public static InteropTransactionData build(SavingsAccountTransaction transaction String currency = savingsAccount.getCurrency().getCode(); BigDecimal runningBalance = transaction.getRunningBalance(savingsAccount.getCurrency()).getAmount(); - LocalDate bookingDateTime = transaction.getTransactionLocalDate(); - LocalDate endOfBalanceLocalDate = transaction.getEndOfBalanceLocalDate(); + LocalDate bookingDateTime = transaction.getTransactionDate(); + LocalDate endOfBalanceLocalDate = transaction.getEndOfBalanceDate(); LocalDate valueDateTime = endOfBalanceLocalDate == null ? bookingDateTime : endOfBalanceLocalDate; StringBuilder sb = new StringBuilder(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionsData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionsData.java index 24a707d28df..34c25ca7dd3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionsData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionsData.java @@ -23,6 +23,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; @@ -41,7 +42,7 @@ public static InteropTransactionsData build(SavingsAccount account, @NotNull Pre } List trans = account.getTransactions().stream().filter(filter).sorted((t1, t2) -> { - int i = t2.getDateOf().compareTo(t1.getDateOf()); + int i = DateUtils.compare(t2.getDateOf(), t1.getDateOf()); return i != 0 ? i : Long.signum(t2.getId() - t1.getId()); }).map(InteropTransactionData::build).collect(Collectors.toList()); return new InteropTransactionsData(account.getId(), trans); diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java index f6d48636323..1c50f0b3c82 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java @@ -30,6 +30,7 @@ import java.time.LocalDateTime; import java.util.Objects; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; @Entity @@ -67,18 +68,17 @@ public class InteropIdentifier extends AbstractPersistableCustom { protected InteropIdentifier() {} public InteropIdentifier(@NotNull SavingsAccount account, @NotNull InteropIdentifierType type, @NotNull String value, String subType, - @NotNull String createdBy, @NotNull LocalDateTime createdOn) { + @NotNull String createdBy) { this.account = account; this.type = type; this.value = value; this.subType = subType; this.createdBy = createdBy; - this.createdOn = createdOn; + this.createdOn = DateUtils.getAuditLocalDateTime(); } - public InteropIdentifier(@NotNull SavingsAccount account, @NotNull InteropIdentifierType type, @NotNull String createdBy, - @NotNull LocalDateTime createdOn) { - this(account, type, null, null, createdBy, createdOn); + public InteropIdentifier(@NotNull SavingsAccount account, @NotNull InteropIdentifierType type, @NotNull String createdBy) { + this(account, type, null, null, createdBy); } public SavingsAccount getAccount() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java index e0746693705..7b884d4fddc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java @@ -38,6 +38,8 @@ import java.util.List; import java.util.Locale; import java.util.function.Predicate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.commands.domain.CommandWrapper; @@ -104,22 +106,17 @@ import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException; import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; import org.apache.fineract.useradministration.domain.AppUser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@Slf4j +@RequiredArgsConstructor public class InteropServiceImpl implements InteropService { - private static final Logger LOG = LoggerFactory.getLogger(InteropServiceImpl.class); - private final PlatformSecurityContext securityContext; private final InteropDataValidator dataValidator; @@ -143,33 +140,6 @@ public class InteropServiceImpl implements InteropService { private final DefaultToApiJsonSerializer toApiJsonSerializer; private final DatabaseSpecificSQLGenerator sqlGenerator; - @Autowired - public InteropServiceImpl(PlatformSecurityContext securityContext, InteropDataValidator interopDataValidator, - SavingsAccountRepository savingsAccountRepository, SavingsAccountTransactionRepository savingsAccountTransactionRepository, - ApplicationCurrencyRepository applicationCurrencyRepository, NoteRepository noteRepository, - PaymentTypeRepository paymentTypeRepository, InteropIdentifierRepository identifierRepository, LoanRepository loanRepository, - SavingsHelper savingsHelper, SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, - SavingsAccountDomainService savingsAccountService, final JdbcTemplate jdbcTemplate, - final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - final DefaultToApiJsonSerializer toApiJsonSerializer, DatabaseSpecificSQLGenerator sqlGenerator) { - this.securityContext = securityContext; - this.dataValidator = interopDataValidator; - this.savingsAccountRepository = savingsAccountRepository; - this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; - this.currencyRepository = applicationCurrencyRepository; - this.noteRepository = noteRepository; - this.paymentTypeRepository = paymentTypeRepository; - this.identifierRepository = identifierRepository; - this.loanRepository = loanRepository; - this.savingsHelper = savingsHelper; - this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; - this.savingsAccountService = savingsAccountService; - this.jdbcTemplate = jdbcTemplate; - this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; - this.toApiJsonSerializer = toApiJsonSerializer; - this.sqlGenerator = sqlGenerator; - } - private static final class KycMapper implements RowMapper { private final DatabaseSpecificSQLGenerator sqlGenerator; @@ -243,7 +213,7 @@ public InteropTransactionsData getAccountTransactions(@NotNull String accountId, return true; } - java.time.LocalDateTime transactionDate = t.getTransactionLocalDate().atStartOfDay(ZoneId.systemDefault()).toLocalDateTime(); + java.time.LocalDateTime transactionDate = t.getTransactionDate().atStartOfDay(ZoneId.systemDefault()).toLocalDateTime(); return (transactionsTo == null || transactionsTo.compareTo(transactionDate) > 0) && (transactionsFrom == null || transactionsFrom.compareTo(transactionDate.withHour(23).withMinute(59).withSecond(59)) <= 0); }; @@ -276,16 +246,15 @@ public InteropIdentifierAccountResponseData getAccountByIdentifier(@NotNull Inte @Override public InteropIdentifierAccountResponseData registerAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType, @NotNull JsonCommand command) { - InteropIdentifierRequestData request = dataValidator.validateAndParseCreateIdentifier(idType, idValue, subIdOrType, command); // TODO: error handling SavingsAccount savingsAccount = validateAndGetSavingAccount(request.getAccountId()); try { - AppUser createdBy = getLoginUser(); + AppUser createdBy = securityContext.authenticatedUser(); InteropIdentifier identifier = new InteropIdentifier(savingsAccount, request.getIdType(), request.getIdValue(), - request.getSubIdOrType(), createdBy.getUsername(), DateUtils.getLocalDateTimeOfTenant()); + request.getSubIdOrType(), createdBy.getUsername()); identifierRepository.saveAndFlush(identifier); @@ -401,10 +370,9 @@ public InteropTransferResponseData prepareTransfer(@NotNull JsonCommand command) PaymentDetail paymentDetail = instance(findPaymentType(), savingsAccount.getExternalId().getValue(), null, getRoutingCode(), transferCode, null); SavingsAccountTransaction holdTransaction = SavingsAccountTransaction.holdAmount(savingsAccount, savingsAccount.office(), - paymentDetail, transactionDate, Money.of(savingsAccount.getCurrency(), total), DateUtils.getLocalDateTimeOfTenant(), - getLoginUser(), false); + paymentDetail, transactionDate, Money.of(savingsAccount.getCurrency(), total), false); MonetaryCurrency accountCurrency = savingsAccount.getCurrency().copy(); - holdTransaction.updateRunningBalance( + holdTransaction.setRunningBalance( Money.of(accountCurrency, savingsAccount.getWithdrawableBalance().subtract(holdTransaction.getAmount()))); holdTransaction.updateCumulativeBalanceAndDates(accountCurrency, transactionDate); @@ -454,8 +422,8 @@ public InteropTransferResponseData commitTransfer(@NotNull JsonCommand command) } if (holdTransaction.getReleaseIdOfHoldAmountTransaction() == null) { - SavingsAccountTransaction releaseTransaction = savingsAccountTransactionRepository.saveAndFlush( - releaseAmount(holdTransaction, transactionDate, DateUtils.getLocalDateTimeOfSystem(), getLoginUser())); + SavingsAccountTransaction releaseTransaction = savingsAccountTransactionRepository + .saveAndFlush(releaseAmount(holdTransaction, transactionDate)); holdTransaction.updateReleaseId(releaseTransaction.getId()); savingsAccount.releaseOnHoldAmount(holdTransaction.getAmount()); savingsAccount.addTransaction(releaseTransaction); @@ -493,11 +461,10 @@ public InteropTransferResponseData commitTransfer(@NotNull JsonCommand command) SavingsAccountTransaction holdTransaction = findTransaction(savingsAccount, request.getTransferCode(), AMOUNT_HOLD.getValue()); if (holdTransaction != null && holdTransaction.getReleaseIdOfHoldAmountTransaction() == null) { - SavingsAccountTransaction releaseTransaction = releaseAmount(holdTransaction, transactionDate, - DateUtils.getLocalDateTimeOfSystem(), getLoginUser()); + SavingsAccountTransaction releaseTransaction = releaseAmount(holdTransaction, transactionDate); MonetaryCurrency accountCurrency = savingsAccount.getCurrency().copy(); - releaseTransaction.updateRunningBalance( - Money.of(accountCurrency, savingsAccount.getWithdrawableBalance().add(holdTransaction.getAmount()))); + releaseTransaction + .setRunningBalance(Money.of(accountCurrency, savingsAccount.getWithdrawableBalance().add(holdTransaction.getAmount()))); releaseTransaction.updateCumulativeBalanceAndDates(accountCurrency, transactionDate); releaseTransaction = savingsAccountTransactionRepository.saveAndFlush(releaseTransaction); holdTransaction.updateReleaseId(releaseTransaction.getId()); @@ -652,10 +619,6 @@ public InteropIdentifier findIdentifier(@NotNull InteropIdentifierType idType, @ return identifierRepository.findOneByTypeAndValueAndSubType(idType, idValue, subIdOrType); } - private AppUser getLoginUser() { - return securityContext.getAuthenticatedUserIfPresent(); - } - /* * Guaranteed to throw an exception no matter what the data integrity issue is. */ @@ -668,7 +631,7 @@ private void handleInteropDataIntegrityIssues(InteropIdentifierType idType, Stri "idType", idType.name(), accountId); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.interop.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/starter/InteroperationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/starter/InteroperationConfiguration.java new file mode 100644 index 00000000000..9f5ce202d31 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/starter/InteroperationConfiguration.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.starter; + +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.interoperation.domain.InteropIdentifierRepository; +import org.apache.fineract.interoperation.serialization.InteropDataValidator; +import org.apache.fineract.interoperation.service.InteropService; +import org.apache.fineract.interoperation.service.InteropServiceImpl; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepository; +import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionSummaryWrapper; +import org.apache.fineract.portfolio.savings.domain.SavingsHelper; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class InteroperationConfiguration { + + @Bean + @ConditionalOnMissingBean(InteropService.class) + public InteropService interopService(PlatformSecurityContext securityContext, InteropDataValidator interopDataValidator, + SavingsAccountRepository savingsAccountRepository, SavingsAccountTransactionRepository savingsAccountTransactionRepository, + ApplicationCurrencyRepository applicationCurrencyRepository, NoteRepository noteRepository, + PaymentTypeRepository paymentTypeRepository, InteropIdentifierRepository identifierRepository, LoanRepository loanRepository, + SavingsHelper savingsHelper, SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, + SavingsAccountDomainService savingsAccountService, JdbcTemplate jdbcTemplate, + PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + DefaultToApiJsonSerializer toApiJsonSerializer, DatabaseSpecificSQLGenerator sqlGenerator) { + return new InteropServiceImpl(securityContext, interopDataValidator, savingsAccountRepository, savingsAccountTransactionRepository, + applicationCurrencyRepository, noteRepository, paymentTypeRepository, identifierRepository, loanRepository, savingsHelper, + savingsAccountTransactionSummaryWrapper, savingsAccountService, jdbcTemplate, commandsSourceWritePlatformService, + toApiJsonSerializer, sqlGenerator); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java index 1e58680a4a1..367ef80c864 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java @@ -20,23 +20,17 @@ import java.sql.ResultSet; import java.sql.SQLException; +import lombok.RequiredArgsConstructor; import org.apache.fineract.mix.data.MixTaxonomyMappingData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class MixTaxonomyMappingReadPlatformServiceImpl implements MixTaxonomyMappingReadPlatformService { private final JdbcTemplate jdbcTemplate; - @Autowired - public MixTaxonomyMappingReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - private static final class TaxonomyMappingMapper implements RowMapper { public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java index ea2bd143a4d..bb6a5424ad4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java @@ -18,27 +18,21 @@ */ package org.apache.fineract.mix.service; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.mix.domain.MixTaxonomyMapping; import org.apache.fineract.mix.domain.MixTaxonomyMappingRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class MixTaxonomyMappingWritePlatformServiceImpl implements MixTaxonomyMappingWritePlatformService { private final MixTaxonomyMappingRepository mappingRepository; - @Autowired - public MixTaxonomyMappingWritePlatformServiceImpl(final MixTaxonomyMappingRepository mappingRepository) { - this.mappingRepository = mappingRepository; - } - @Transactional @Override public CommandProcessingResult updateMapping(final Long mappingId, final JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java index f807cee5c97..eb4eb0fb715 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java @@ -22,18 +22,14 @@ import java.sql.SQLException; import java.util.List; import org.apache.fineract.mix.data.MixTaxonomyData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service public class MixTaxonomyReadPlatformServiceImpl implements MixTaxonomyReadPlatformService { private final JdbcTemplate jdbcTemplate; private final MixTaxonomyMapper mixTaxonomyMapper; - @Autowired public MixTaxonomyReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; this.mixTaxonomyMapper = new MixTaxonomyMapper(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java index d5ca291cf1f..0ba7329530c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java @@ -21,18 +21,14 @@ import java.sql.ResultSet; import java.sql.SQLException; import org.apache.fineract.mix.data.NamespaceData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service public class NamespaceReadPlatformServiceImpl implements NamespaceReadPlatformService { private final JdbcTemplate jdbcTemplate; private final NamespaceMapper namespaceMapper; - @Autowired public NamespaceReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; this.namespaceMapper = new NamespaceMapper(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java index 8a487fc2883..0628a4f6a89 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java @@ -30,21 +30,20 @@ import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.mix.data.MixTaxonomyData; import org.apache.fineract.mix.data.MixTaxonomyMappingData; import org.apache.fineract.mix.data.XBRLData; import org.apache.fineract.mix.exception.XBRLMappingInvalidException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.stereotype.Component; @Component +@Slf4j public class XBRLResultServiceImpl implements XBRLResultService { - private static final Logger LOG = LoggerFactory.getLogger(XBRLResultServiceImpl.class); private static final ScriptEngine SCRIPT_ENGINE = new ScriptEngineManager().getEngineByName("JavaScript"); private final MixTaxonomyMappingReadPlatformService readTaxonomyMappingService; @@ -162,7 +161,7 @@ private BigDecimal processMappingString(Map accountBalanceMa eval = value.floatValue(); } } catch (final ScriptException e) { - LOG.error("Problem occurred in processMappingString function", e); + log.error("Problem occurred in processMappingString function", e); throw new IllegalArgumentException(e.getMessage(), e); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/starter/MixConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/mix/starter/MixConfiguration.java new file mode 100644 index 00000000000..b4d9f25d664 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/mix/starter/MixConfiguration.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.starter; + +import org.apache.fineract.mix.domain.MixTaxonomyMappingRepository; +import org.apache.fineract.mix.service.MixTaxonomyMappingReadPlatformService; +import org.apache.fineract.mix.service.MixTaxonomyMappingReadPlatformServiceImpl; +import org.apache.fineract.mix.service.MixTaxonomyMappingWritePlatformService; +import org.apache.fineract.mix.service.MixTaxonomyMappingWritePlatformServiceImpl; +import org.apache.fineract.mix.service.MixTaxonomyReadPlatformService; +import org.apache.fineract.mix.service.MixTaxonomyReadPlatformServiceImpl; +import org.apache.fineract.mix.service.NamespaceReadPlatformService; +import org.apache.fineract.mix.service.NamespaceReadPlatformServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class MixConfiguration { + + @Bean + @ConditionalOnMissingBean(MixTaxonomyMappingReadPlatformService.class) + public MixTaxonomyMappingReadPlatformService mixTaxonomyMappingReadPlatformService(JdbcTemplate jdbcTemplate) { + return new MixTaxonomyMappingReadPlatformServiceImpl(jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(MixTaxonomyMappingWritePlatformService.class) + public MixTaxonomyMappingWritePlatformService mixTaxonomyMappingWritePlatformService(MixTaxonomyMappingRepository mappingRepository) { + return new MixTaxonomyMappingWritePlatformServiceImpl(mappingRepository); + } + + @Bean + @ConditionalOnMissingBean(MixTaxonomyReadPlatformService.class) + public MixTaxonomyReadPlatformService mixTaxonomyReadPlatformService(JdbcTemplate jdbcTemplate) { + return new MixTaxonomyReadPlatformServiceImpl(jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(NamespaceReadPlatformService.class) + public NamespaceReadPlatformService namespaceReadPlatformService(JdbcTemplate jdbcTemplate) { + return new NamespaceReadPlatformServiceImpl(jdbcTemplate); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java index e6efa256747..4a9f2232c98 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java @@ -55,9 +55,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor @Slf4j public class NotificationDomainServiceImpl implements NotificationDomainService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationGeneratorReadRepositoryWrapperImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationGeneratorReadRepositoryWrapperImpl.java index 1267aa5857d..fcec6503cb8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationGeneratorReadRepositoryWrapperImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationGeneratorReadRepositoryWrapperImpl.java @@ -19,21 +19,17 @@ package org.apache.fineract.notification.service; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.notification.domain.Notification; import org.apache.fineract.notification.domain.NotificationRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class NotificationGeneratorReadRepositoryWrapperImpl implements NotificationGeneratorReadRepositoryWrapper { private final NotificationRepository notificationRepository; - @Autowired - public NotificationGeneratorReadRepositoryWrapperImpl(NotificationRepository notificationRepository) { - this.notificationRepository = notificationRepository; - } - @Override public Notification findById(Long id) { return this.notificationRepository.findById(id).orElse(null); diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationMapperReadRepositoryWrapperImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationMapperReadRepositoryWrapperImpl.java index 91f879825a4..a332a45a327 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationMapperReadRepositoryWrapperImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationMapperReadRepositoryWrapperImpl.java @@ -19,21 +19,17 @@ package org.apache.fineract.notification.service; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.notification.domain.NotificationMapper; import org.apache.fineract.notification.domain.NotificationMapperRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class NotificationMapperReadRepositoryWrapperImpl implements NotificationMapperReadRepositoryWrapper { private final NotificationMapperRepository notificationMapperRepository; - @Autowired - public NotificationMapperReadRepositoryWrapperImpl(NotificationMapperRepository notificationMapperRepository) { - this.notificationMapperRepository = notificationMapperRepository; - } - @Override public NotificationMapper findById(Long id) { return this.notificationMapperRepository.findById(id).orElse(null); diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationMapperWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationMapperWritePlatformServiceImpl.java index e74dcc2762f..c2b95b5f813 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationMapperWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationMapperWritePlatformServiceImpl.java @@ -18,21 +18,17 @@ */ package org.apache.fineract.notification.service; +import lombok.RequiredArgsConstructor; import org.apache.fineract.notification.domain.NotificationMapper; import org.apache.fineract.notification.domain.NotificationMapperRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class NotificationMapperWritePlatformServiceImpl implements NotificationMapperWritePlatformService { private final NotificationMapperRepository notificationMapperRepository; - @Autowired - public NotificationMapperWritePlatformServiceImpl(NotificationMapperRepository notificationMapperRepository) { - this.notificationMapperRepository = notificationMapperRepository; - } - @Override public Long create(NotificationMapper notificationMapper) { this.notificationMapperRepository.saveAndFlush(notificationMapper); diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java index ae19bad6a19..1ddd1129f0e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java @@ -35,9 +35,7 @@ import org.apache.fineract.notification.data.NotificationMapperData; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class NotificationReadPlatformServiceImpl implements NotificationReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java index e5a5b68f40b..a459c74b0f9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java @@ -27,10 +27,8 @@ import org.apache.fineract.notification.domain.NotificationMapper; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUserRepository; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service @Transactional @RequiredArgsConstructor public class NotificationWritePlatformServiceImpl implements NotificationWritePlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationServiceImpl.java index f597aa4a179..5f06f89869a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationServiceImpl.java @@ -33,9 +33,7 @@ import org.apache.fineract.notification.eventandlistener.NotificationEventPublisher; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUserRepository; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor @Slf4j public class UserNotificationServiceImpl implements UserNotificationService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/starter/NotificationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/notification/starter/NotificationConfiguration.java new file mode 100644 index 00000000000..4c8766c3838 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/starter/NotificationConfiguration.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.notification.starter; + +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.notification.eventandlistener.NotificationEventPublisher; +import org.apache.fineract.notification.service.NotificationDomainService; +import org.apache.fineract.notification.service.NotificationDomainServiceImpl; +import org.apache.fineract.notification.service.NotificationGeneratorReadRepositoryWrapper; +import org.apache.fineract.notification.service.NotificationGeneratorWritePlatformService; +import org.apache.fineract.notification.service.NotificationMapperWritePlatformService; +import org.apache.fineract.notification.service.NotificationReadPlatformService; +import org.apache.fineract.notification.service.NotificationReadPlatformServiceImpl; +import org.apache.fineract.notification.service.NotificationWritePlatformService; +import org.apache.fineract.notification.service.NotificationWritePlatformServiceImpl; +import org.apache.fineract.notification.service.UserNotificationService; +import org.apache.fineract.notification.service.UserNotificationServiceImpl; +import org.apache.fineract.useradministration.domain.AppUserRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class NotificationConfiguration { + + @Bean + @ConditionalOnMissingBean(NotificationDomainService.class) + public NotificationDomainService notificationDomainService(BusinessEventNotifierService businessEventNotifierService, + PlatformSecurityContext context, UserNotificationService userNotificationService) { + return new NotificationDomainServiceImpl(businessEventNotifierService, context, userNotificationService); + } + + @Bean + @ConditionalOnMissingBean(NotificationReadPlatformService.class) + public NotificationReadPlatformService notificationReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context, + ColumnValidator columnValidator, PaginationHelper paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator) { + return new NotificationReadPlatformServiceImpl(jdbcTemplate, context, columnValidator, paginationHelper, sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(NotificationWritePlatformService.class) + public NotificationWritePlatformService notificationWritePlatformService( + NotificationGeneratorWritePlatformService notificationGeneratorWritePlatformService, + NotificationGeneratorReadRepositoryWrapper notificationGeneratorReadRepositoryWrapper, AppUserRepository appUserRepository, + NotificationMapperWritePlatformService notificationMapperWritePlatformService) { + return new NotificationWritePlatformServiceImpl(notificationGeneratorWritePlatformService, + notificationGeneratorReadRepositoryWrapper, appUserRepository, notificationMapperWritePlatformService); + } + + @Bean + @ConditionalOnMissingBean(UserNotificationService.class) + public UserNotificationService userNotificationService(NotificationEventPublisher notificationEventPublisher, + AppUserRepository appUserRepository, FineractProperties fineractProperties, + NotificationReadPlatformService notificationReadPlatformService, + NotificationWritePlatformService notificationWritePlatformService) { + return new UserNotificationServiceImpl(notificationEventPublisher, appUserRepository, fineractProperties, + notificationReadPlatformService, notificationWritePlatformService); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java index 5acfd0c7b89..94f9e7b058c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -32,24 +33,16 @@ import org.apache.fineract.organisation.holiday.data.HolidayData; import org.apache.fineract.organisation.holiday.domain.RescheduleType; import org.apache.fineract.organisation.holiday.exception.HolidayNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformService { private final PlatformSecurityContext context; private final JdbcTemplate jdbcTemplate; - @Autowired - public HolidayReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - } - private static final class HolidayMapper implements RowMapper { private final String schema; diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java index c097d42c49c..22d0acc027a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java @@ -27,12 +27,15 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.holiday.api.HolidayApiConstants; import org.apache.fineract.organisation.holiday.data.HolidayDataValidator; @@ -44,19 +47,14 @@ import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@Slf4j +@RequiredArgsConstructor public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(HolidayWritePlatformServiceJpaRepositoryImpl.class); - private final HolidayDataValidator fromApiJsonDeserializer; private final HolidayRepositoryWrapper holidayRepository; private final WorkingDaysRepositoryWrapper daysRepositoryWrapper; @@ -64,19 +62,6 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit private final OfficeRepositoryWrapper officeRepositoryWrapper; private final FromJsonHelper fromApiJsonHelper; - @Autowired - public HolidayWritePlatformServiceJpaRepositoryImpl(final HolidayDataValidator fromApiJsonDeserializer, - final HolidayRepositoryWrapper holidayRepository, final PlatformSecurityContext context, - final OfficeRepositoryWrapper officeRepositoryWrapper, final FromJsonHelper fromApiJsonHelper, - final WorkingDaysRepositoryWrapper daysRepositoryWrapper) { - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - this.holidayRepository = holidayRepository; - this.context = context; - this.officeRepositoryWrapper = officeRepositoryWrapper; - this.fromApiJsonHelper = fromApiJsonHelper; - this.daysRepositoryWrapper = daysRepositoryWrapper; - } - @Transactional @Override public CommandProcessingResult createHoliday(final JsonCommand command) { @@ -184,7 +169,7 @@ private void handleDataIntegrityIssues(final JsonCommand command, final Throwabl "name", name); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.office.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } @@ -206,18 +191,15 @@ private void validateInputDates(final JsonCommand command) { } private void validateInputDates(final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo) { - String defaultUserMessage = ""; - if (toDate.isBefore(fromDate)) { + if (DateUtils.isBefore(toDate, fromDate)) { defaultUserMessage = "To Date date cannot be before the From Date."; throw new HolidayDateException("to.date.cannot.be.before.from.date", defaultUserMessage, fromDate.toString(), toDate.toString()); } if (repaymentsRescheduledTo != null) { - if ((repaymentsRescheduledTo.isEqual(fromDate) || repaymentsRescheduledTo.isEqual(toDate) - || (repaymentsRescheduledTo.isAfter(fromDate) && repaymentsRescheduledTo.isBefore(toDate)))) { - + if (!DateUtils.isBefore(repaymentsRescheduledTo, fromDate) && !DateUtils.isAfter(repaymentsRescheduledTo, toDate)) { defaultUserMessage = "Repayments rescheduled date should be before from date or after to date."; throw new HolidayDateException("repayments.rescheduled.date.should.be.before.from.date.or.after.to.date", defaultUserMessage, repaymentsRescheduledTo.toString()); @@ -235,13 +217,12 @@ private void validateInputDates(final LocalDate fromDate, final LocalDate toDate // validate repaymentsRescheduledTo date // 1. should be within a 30 days date range. // 2. Alternative date should not be an exist holiday.//TBD - // 3. Holiday should not be on an repaymentsRescheduledTo date of - // another holiday.//TBD + // 3. Holiday should not be on an repaymentsRescheduledTo date of another holiday.//TBD - // restricting repaymentsRescheduledTo date to be within 30 days - // range - // before or after from date and to date. - if (repaymentsRescheduledTo.isBefore(fromDate.minusDays(30)) || repaymentsRescheduledTo.isAfter(toDate.plusDays(30))) { + // restricting repaymentsRescheduledTo date to be within 30 days range before or after from date and to + // date. + if (DateUtils.isBefore(repaymentsRescheduledTo, fromDate.minusDays(30)) + || DateUtils.isAfter(repaymentsRescheduledTo, toDate.plusDays(30))) { defaultUserMessage = "Repayments Rescheduled to date must be within 30 days before or after from and to dates"; throw new HolidayDateException("repayments.rescheduled.to.must.be.within.range", defaultUserMessage, fromDate.toString(), toDate.toString(), repaymentsRescheduledTo.toString()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/starter/OrganisationHolidayConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/starter/OrganisationHolidayConfiguration.java new file mode 100644 index 00000000000..f59962ff1ef --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/starter/OrganisationHolidayConfiguration.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.holiday.starter; + +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.holiday.data.HolidayDataValidator; +import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper; +import org.apache.fineract.organisation.holiday.service.HolidayReadPlatformService; +import org.apache.fineract.organisation.holiday.service.HolidayReadPlatformServiceImpl; +import org.apache.fineract.organisation.holiday.service.HolidayWritePlatformService; +import org.apache.fineract.organisation.holiday.service.HolidayWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class OrganisationHolidayConfiguration { + + @Bean + @ConditionalOnMissingBean(HolidayReadPlatformService.class) + public HolidayReadPlatformService holidayReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate) { + return new HolidayReadPlatformServiceImpl(context, jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(HolidayWritePlatformService.class) + public HolidayWritePlatformService holidayWritePlatformService(HolidayDataValidator fromApiJsonDeserializer, + HolidayRepositoryWrapper holidayRepository, PlatformSecurityContext context, OfficeRepositoryWrapper officeRepositoryWrapper, + FromJsonHelper fromApiJsonHelper, WorkingDaysRepositoryWrapper daysRepositoryWrapper) { + return new HolidayWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, holidayRepository, daysRepositoryWrapper, context, + officeRepositoryWrapper, fromApiJsonHelper); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java index d42db5759c0..26bd1e7ae4e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java @@ -21,27 +21,19 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformService { - private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - private final CurrencyMapper currencyRowMapper; - - @Autowired - public CurrencyReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - this.currencyRowMapper = new CurrencyMapper(); - } + private final JdbcTemplate jdbcTemplate; + private final CurrencyMapper currencyRowMapper = new CurrencyMapper(); @Override public Collection retrieveAllowedCurrencies() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java index 1657c47578c..8ae1e81b74b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; @@ -37,11 +38,9 @@ import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class CurrencyWritePlatformServiceJpaRepositoryImpl implements CurrencyWritePlatformService { private final PlatformSecurityContext context; @@ -52,21 +51,6 @@ public class CurrencyWritePlatformServiceJpaRepositoryImpl implements CurrencyWr private final SavingsProductReadPlatformService savingsProductService; private final ChargeReadPlatformService chargeService; - @Autowired - public CurrencyWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final CurrencyCommandFromApiJsonDeserializer fromApiJsonDeserializer, - final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, - final OrganisationCurrencyRepository organisationCurrencyRepository, final LoanProductReadPlatformService loanProductService, - final SavingsProductReadPlatformService savingsProductService, final ChargeReadPlatformService chargeService) { - this.context = context; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - this.applicationCurrencyRepository = applicationCurrencyRepository; - this.organisationCurrencyRepository = organisationCurrencyRepository; - this.loanProductService = loanProductService; - this.savingsProductService = savingsProductService; - this.chargeService = chargeService; - } - @Transactional @Override public CommandProcessingResult updateAllowedCurrencies(final JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java index 9135c11f7cf..ee756d2b641 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java @@ -19,21 +19,15 @@ package org.apache.fineract.organisation.monetary.service; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.organisation.monetary.data.ApplicationCurrencyConfigurationData; import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class OrganisationCurrencyReadPlatformServiceImpl implements OrganisationCurrencyReadPlatformService { private final CurrencyReadPlatformService currencyReadPlatformService; - @Autowired - public OrganisationCurrencyReadPlatformServiceImpl(final CurrencyReadPlatformService currencyReadPlatformService) { - this.currencyReadPlatformService = currencyReadPlatformService; - } - @Override public ApplicationCurrencyConfigurationData retrieveCurrencyConfiguration() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/starter/OrganisationMonetaryConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/starter/OrganisationMonetaryConfiguration.java new file mode 100644 index 00000000000..cc5e6219737 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/starter/OrganisationMonetaryConfiguration.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.monetary.starter; + +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.apache.fineract.organisation.monetary.serialization.CurrencyCommandFromApiJsonDeserializer; +import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; +import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformServiceImpl; +import org.apache.fineract.organisation.monetary.service.CurrencyWritePlatformService; +import org.apache.fineract.organisation.monetary.service.CurrencyWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.organisation.monetary.service.OrganisationCurrencyReadPlatformService; +import org.apache.fineract.organisation.monetary.service.OrganisationCurrencyReadPlatformServiceImpl; +import org.apache.fineract.organisation.office.domain.OrganisationCurrencyRepository; +import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; +import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class OrganisationMonetaryConfiguration { + + @Bean + @ConditionalOnMissingBean(CurrencyReadPlatformService.class) + public CurrencyReadPlatformService currencyReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate) { + return new CurrencyReadPlatformServiceImpl(context, jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(CurrencyWritePlatformService.class) + public CurrencyWritePlatformService currencyWritePlatformService(PlatformSecurityContext context, + ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, + OrganisationCurrencyRepository organisationCurrencyRepository, CurrencyCommandFromApiJsonDeserializer fromApiJsonDeserializer, + LoanProductReadPlatformService loanProductService, SavingsProductReadPlatformService savingsProductService, + ChargeReadPlatformService chargeService) { + return new CurrencyWritePlatformServiceJpaRepositoryImpl(context, applicationCurrencyRepository, organisationCurrencyRepository, + fromApiJsonDeserializer, loanProductService, savingsProductService, chargeService); + } + + @Bean + @ConditionalOnMissingBean(OrganisationCurrencyReadPlatformService.class) + public OrganisationCurrencyReadPlatformService organisationCurrencyReadPlatformService( + CurrencyReadPlatformService currencyReadPlatformService) { + return new OrganisationCurrencyReadPlatformServiceImpl(currencyReadPlatformService); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java index e606fa32341..3b8b4914e4e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java @@ -45,9 +45,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java index a2e6395d74d..4373e657f5b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java @@ -44,10 +44,8 @@ import org.springframework.cache.annotation.Caching; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service @Slf4j @RequiredArgsConstructor public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWritePlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/starter/OrganisationOfficeConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/starter/OrganisationOfficeConfiguration.java new file mode 100644 index 00000000000..e07746e6c90 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/starter/OrganisationOfficeConfiguration.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.office.starter; + +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; +import org.apache.fineract.organisation.office.domain.OfficeRepository; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.apache.fineract.organisation.office.domain.OfficeTransactionRepository; +import org.apache.fineract.organisation.office.mapper.OfficeDataMapper; +import org.apache.fineract.organisation.office.serialization.OfficeCommandFromApiJsonDeserializer; +import org.apache.fineract.organisation.office.serialization.OfficeTransactionCommandFromApiJsonDeserializer; +import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; +import org.apache.fineract.organisation.office.service.OfficeReadPlatformServiceImpl; +import org.apache.fineract.organisation.office.service.OfficeWritePlatformService; +import org.apache.fineract.organisation.office.service.OfficeWritePlatformServiceJpaRepositoryImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class OrganisationOfficeConfiguration { + + @Bean + @ConditionalOnMissingBean(OfficeReadPlatformService.class) + public OfficeReadPlatformService officeReadPlatformService(JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator, + PlatformSecurityContext context, CurrencyReadPlatformService currencyReadPlatformService, ColumnValidator columnValidator, + OfficeRepository officeRepository, OfficeDataMapper officeDataMapper) { + return new OfficeReadPlatformServiceImpl(jdbcTemplate, sqlGenerator, context, currencyReadPlatformService, columnValidator, + officeRepository, officeDataMapper); + } + + @Bean + @ConditionalOnMissingBean(OfficeWritePlatformService.class) + public OfficeWritePlatformService officeWritePlatformService(PlatformSecurityContext context, + OfficeCommandFromApiJsonDeserializer fromApiJsonDeserializer, + OfficeTransactionCommandFromApiJsonDeserializer moneyTransferCommandFromApiJsonDeserializer, + OfficeRepositoryWrapper officeRepositoryWrapper, OfficeTransactionRepository officeTransactionRepository, + ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository) { + return new OfficeWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, + moneyTransferCommandFromApiJsonDeserializer, officeRepositoryWrapper, officeTransactionRepository, + applicationCurrencyRepository); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformServiceImpl.java index 656bcb2f113..113d61099aa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryReadPlatformServiceImpl.java @@ -21,24 +21,17 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.organisation.provisioning.data.ProvisioningCategoryData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ProvisioningCategoryReadPlatformServiceImpl implements ProvisioningCategoryReadPlatformService { private final JdbcTemplate jdbcTemplate; - private final ProvisioningCategoryRowMapper provisionCategoryRowMapper; - - @Autowired - public ProvisioningCategoryReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - this.provisionCategoryRowMapper = new ProvisioningCategoryRowMapper(); - } + private final ProvisioningCategoryRowMapper provisionCategoryRowMapper = new ProvisioningCategoryRowMapper(); @Override public Collection retrieveAllProvisionCategories() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl.java index f258a2cf492..6d089d29716 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl.java @@ -20,6 +20,8 @@ import jakarta.persistence.PersistenceException; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -30,33 +32,20 @@ import org.apache.fineract.organisation.provisioning.exception.ProvisioningCategoryCannotBeDeletedException; import org.apache.fineract.organisation.provisioning.exception.ProvisioningCategoryNotFoundException; import org.apache.fineract.organisation.provisioning.serialization.ProvisioningCategoryDefinitionJsonDeserializer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.NonTransientDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; -@Service +@Slf4j +@RequiredArgsConstructor public class ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl implements ProvisioningCategoryWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl.class); - private final ProvisioningCategoryRepository provisioningCategoryRepository; private final ProvisioningCategoryDefinitionJsonDeserializer fromApiJsonDeserializer; private final JdbcTemplate jdbcTemplate; - @Autowired - public ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl(final ProvisioningCategoryRepository provisioningCategoryRepository, - final ProvisioningCategoryDefinitionJsonDeserializer fromApiJsonDeserializer, final JdbcTemplate jdbcTemplate) { - this.provisioningCategoryRepository = provisioningCategoryRepository; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - this.jdbcTemplate = jdbcTemplate; - } - @Override public CommandProcessingResult createProvisioningCateogry(JsonCommand command) { try { @@ -127,7 +116,7 @@ private void handleDataIntegrityIssues(final JsonCommand command, final Throwabl throw new PlatformDataIntegrityException("error.msg.provisioning.duplicate.categoryname", "Provisioning Cateory with name `" + name + "` already exists", "category name", name); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.charge.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } @@ -139,7 +128,7 @@ private void handleDataIntegrityIssues(final JsonCommand command, final Throwabl throw new PlatformDataIntegrityException("error.msg.provisioning.duplicate.categoryname", "Provisioning Cateory with name `" + name + "` already exists", "category name", name); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.charge.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaAssembler.java index 252b3475b3b..5706a2199ef 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaAssembler.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.accounting.glaccount.domain.GLAccount; import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; @@ -41,10 +42,8 @@ import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaOverlappingDefinitionException; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ProvisioningCriteriaAssembler { private final FromJsonHelper fromApiJsonHelper; @@ -53,17 +52,6 @@ public class ProvisioningCriteriaAssembler { private final GLAccountRepository glAccountRepository; private final PlatformSecurityContext platformSecurityContext; - @Autowired - public ProvisioningCriteriaAssembler(final FromJsonHelper fromApiJsonHelper, - final ProvisioningCategoryRepository provisioningCategoryRepository, final LoanProductRepository loanProductRepository, - final GLAccountRepository glAccountRepository, final PlatformSecurityContext platformSecurityContext) { - this.fromApiJsonHelper = fromApiJsonHelper; - this.provisioningCategoryRepository = provisioningCategoryRepository; - this.loanProductRepository = loanProductRepository; - this.glAccountRepository = glAccountRepository; - this.platformSecurityContext = platformSecurityContext; - } - public List parseLoanProducts(final JsonElement jsonElement) { List loanProducts = new ArrayList<>(); if (fromApiJsonHelper.parameterExists(ProvisioningCriteriaConstants.JSON_LOANPRODUCTS_PARAM, jsonElement)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java index 93c69897245..1dde37c06a4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.accounting.glaccount.data.GLAccountData; import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService; import org.apache.fineract.organisation.provisioning.data.ProvisioningCategoryData; @@ -32,13 +33,11 @@ import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaNotFoundException; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ProvisioningCriteriaReadPlatformServiceImpl implements ProvisioningCriteriaReadPlatformService { private final JdbcTemplate jdbcTemplate; @@ -47,19 +46,6 @@ public class ProvisioningCriteriaReadPlatformServiceImpl implements Provisioning private final GLAccountReadPlatformService glAccountReadPlatformService; private final LoanProductReadPlatformService loanProductReaPlatformService; - @Autowired - public ProvisioningCriteriaReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, - final ProvisioningCategoryReadPlatformService provisioningCategoryReadPlatformService, - final LoanProductReadPlatformService loanProductReadPlatformService, - final GLAccountReadPlatformService glAccountReadPlatformService, - final LoanProductReadPlatformService loanProductReaPlatformService) { - this.jdbcTemplate = jdbcTemplate; - this.provisioningCategoryReadPlatformService = provisioningCategoryReadPlatformService; - this.loanProductReadPlatformService = loanProductReadPlatformService; - this.glAccountReadPlatformService = glAccountReadPlatformService; - this.loanProductReaPlatformService = loanProductReaPlatformService; - } - @Override public ProvisioningCriteriaData retrievePrivisiongCriteriaTemplate() { boolean onlyActive = true; diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.java index 1b0d0396ce2..4a0c60baba0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.java @@ -26,6 +26,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.accounting.glaccount.domain.GLAccount; import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository; @@ -44,18 +46,13 @@ import org.apache.fineract.organisation.provisioning.exception.ProvisioningCriteriaNotFoundException; import org.apache.fineract.organisation.provisioning.serialization.ProvisioningCriteriaDefinitionJsonDeserializer; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; -@Service +@Slf4j +@RequiredArgsConstructor public class ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl implements ProvisioningCriteriaWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.class); - private final ProvisioningCriteriaDefinitionJsonDeserializer fromApiJsonDeserializer; private final ProvisioningCriteriaAssembler provisioningCriteriaAssembler; private final ProvisioningCriteriaRepository provisioningCriteriaRepository; @@ -63,21 +60,6 @@ public class ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl implement private final GLAccountRepository glAccountRepository; private final ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService; - @Autowired - public ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl( - final ProvisioningCriteriaDefinitionJsonDeserializer fromApiJsonDeserializer, - final ProvisioningCriteriaAssembler provisioningCriteriaAssembler, - final ProvisioningCriteriaRepository provisioningCriteriaRepository, final FromJsonHelper fromApiJsonHelper, - final GLAccountRepository glAccountRepository, - final ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService) { - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - this.provisioningCriteriaAssembler = provisioningCriteriaAssembler; - this.provisioningCriteriaRepository = provisioningCriteriaRepository; - this.fromApiJsonHelper = fromApiJsonHelper; - this.glAccountRepository = glAccountRepository; - this.provisioningEntriesReadPlatformService = provisioningEntriesReadPlatformService; - } - @Override public CommandProcessingResult createProvisioningCriteria(JsonCommand command) { try { @@ -175,7 +157,7 @@ private void handleDataIntegrityIssues(final JsonCommand command, final Throwabl throw new PlatformDataIntegrityException("error.msg.provisioning.product.id(s).already.associated.existing.criteria", "The selected products already associated with another Provisioning Criteria"); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.provisioning.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/starter/OrganisationProvisioningConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/starter/OrganisationProvisioningConfiguration.java new file mode 100644 index 00000000000..aaa6de8a515 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/starter/OrganisationProvisioningConfiguration.java @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.provisioning.starter; + +import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository; +import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService; +import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesReadPlatformService; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.provisioning.domain.ProvisioningCategoryRepository; +import org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteriaRepository; +import org.apache.fineract.organisation.provisioning.serialization.ProvisioningCategoryDefinitionJsonDeserializer; +import org.apache.fineract.organisation.provisioning.serialization.ProvisioningCriteriaDefinitionJsonDeserializer; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCategoryReadPlatformService; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCategoryReadPlatformServiceImpl; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCategoryWritePlatformService; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaAssembler; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaReadPlatformService; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaReadPlatformServiceImpl; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaWritePlatformService; +import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class OrganisationProvisioningConfiguration { + + @Bean + @ConditionalOnMissingBean(ProvisioningCategoryReadPlatformService.class) + public ProvisioningCategoryReadPlatformService provisioningCategoryReadPlatformService(JdbcTemplate jdbcTemplate) { + return new ProvisioningCategoryReadPlatformServiceImpl(jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(ProvisioningCategoryWritePlatformService.class) + public ProvisioningCategoryWritePlatformService provisioningCategoryWritePlatformService( + ProvisioningCategoryRepository provisioningCategoryRepository, + ProvisioningCategoryDefinitionJsonDeserializer fromApiJsonDeserializer, JdbcTemplate jdbcTemplate) { + return new ProvisioningCategoryWritePlatformServiceJpaRepositoryImpl(provisioningCategoryRepository, fromApiJsonDeserializer, + jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(ProvisioningCriteriaAssembler.class) + public ProvisioningCriteriaAssembler provisioningCriteriaAssembler(FromJsonHelper fromApiJsonHelper, + ProvisioningCategoryRepository provisioningCategoryRepository, LoanProductRepository loanProductRepository, + GLAccountRepository glAccountRepository, PlatformSecurityContext platformSecurityContext) { + return new ProvisioningCriteriaAssembler(fromApiJsonHelper, provisioningCategoryRepository, loanProductRepository, + glAccountRepository, platformSecurityContext); + } + + @Bean + @ConditionalOnMissingBean(ProvisioningCriteriaReadPlatformService.class) + public ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService(JdbcTemplate jdbcTemplate, + ProvisioningCategoryReadPlatformService provisioningCategoryReadPlatformService, + LoanProductReadPlatformService loanProductReadPlatformService, GLAccountReadPlatformService glAccountReadPlatformService, + LoanProductReadPlatformService loanProductReaPlatformService) { + return new ProvisioningCriteriaReadPlatformServiceImpl(jdbcTemplate, provisioningCategoryReadPlatformService, + loanProductReadPlatformService, glAccountReadPlatformService, loanProductReaPlatformService); + } + + @Bean + @ConditionalOnMissingBean(ProvisioningCriteriaWritePlatformService.class) + public ProvisioningCriteriaWritePlatformService provisioningCriteriaWritePlatformService( + ProvisioningCriteriaDefinitionJsonDeserializer fromApiJsonDeserializer, + ProvisioningCriteriaAssembler provisioningCriteriaAssembler, ProvisioningCriteriaRepository provisioningCriteriaRepository, + FromJsonHelper fromApiJsonHelper, GLAccountRepository glAccountRepository, + ProvisioningEntriesReadPlatformService provisioningEntriesReadPlatformService) { + return new ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, provisioningCriteriaAssembler, + provisioningCriteriaRepository, fromApiJsonHelper, glAccountRepository, provisioningEntriesReadPlatformService); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformServiceImpl.java index 8439da90102..57e6ad93a60 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffReadPlatformServiceImpl.java @@ -25,6 +25,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; @@ -34,26 +35,19 @@ import org.apache.fineract.portfolio.client.domain.ClientStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class StaffReadPlatformServiceImpl implements StaffReadPlatformService { - private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; + private final JdbcTemplate jdbcTemplate; + private static final StaffLookupMapper LOOKUP_MAPPER = new StaffLookupMapper(); private static final StaffInOfficeHierarchyMapper STAFF_IN_OFFICE_HIERARCHY_MAPPER = new StaffInOfficeHierarchyMapper(); - @Autowired - public StaffReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - } - private static final class StaffMapper implements RowMapper { public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java index 36e168ce958..3985e9f8b3e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java @@ -20,6 +20,8 @@ import jakarta.persistence.PersistenceException; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -32,31 +34,18 @@ import org.apache.fineract.organisation.staff.domain.StaffRepository; import org.apache.fineract.organisation.staff.exception.StaffNotFoundException; import org.apache.fineract.organisation.staff.serialization.StaffCommandFromApiJsonDeserializer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@Slf4j +@RequiredArgsConstructor public class StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(StaffWritePlatformServiceJpaRepositoryImpl.class); - private final StaffCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final StaffRepository staffRepository; private final OfficeRepositoryWrapper officeRepositoryWrapper; - @Autowired - public StaffWritePlatformServiceJpaRepositoryImpl(final StaffCommandFromApiJsonDeserializer fromApiJsonDeserializer, - final StaffRepository staffRepository, final OfficeRepositoryWrapper officeRepositoryWrapper) { - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - this.staffRepository = staffRepository; - this.officeRepositoryWrapper = officeRepositoryWrapper; - } - @Transactional @Override public CommandProcessingResult createStaff(final JsonCommand command) { @@ -138,7 +127,7 @@ private void handleStaffDataIntegrityIssues(final JsonCommand command, final Thr "A staff with the given display name '" + displayName + "' already exists", "displayName", displayName); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.staff.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/starter/OrganisationStaffConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/starter/OrganisationStaffConfiguration.java new file mode 100644 index 00000000000..d037b2b3408 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/starter/OrganisationStaffConfiguration.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.staff.starter; + +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.apache.fineract.organisation.staff.domain.StaffRepository; +import org.apache.fineract.organisation.staff.serialization.StaffCommandFromApiJsonDeserializer; +import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; +import org.apache.fineract.organisation.staff.service.StaffReadPlatformServiceImpl; +import org.apache.fineract.organisation.staff.service.StaffWritePlatformService; +import org.apache.fineract.organisation.staff.service.StaffWritePlatformServiceJpaRepositoryImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class OrganisationStaffConfiguration { + + @Bean + @ConditionalOnMissingBean(StaffReadPlatformService.class) + public StaffReadPlatformService staffReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate) { + return new StaffReadPlatformServiceImpl(context, jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(StaffWritePlatformService.class) + public StaffWritePlatformService staffWritePlatformService(StaffCommandFromApiJsonDeserializer fromApiJsonDeserializer, + StaffRepository staffRepository, OfficeRepositoryWrapper officeRepositoryWrapper) { + return new StaffWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, staffRepository, officeRepositoryWrapper); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionDataValidator.java index 2305f7a58d9..dc500616df6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionDataValidator.java @@ -20,10 +20,10 @@ import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZonedDateTime; +import java.time.OffsetDateTime; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.organisation.teller.domain.Cashier; import org.apache.fineract.organisation.teller.domain.Teller; @@ -54,7 +54,6 @@ public CashierTransactionDataValidator(final TellerManagementReadPlatformService } public void validateSettleCashAndCashOutTransactions(final Long cashierId, String currencyCode, final BigDecimal transactionAmount) { - final Integer offset = null; final Integer limit = null; final String orderBy = null; @@ -64,7 +63,7 @@ public void validateSettleCashAndCashOutTransactions(final Long cashierId, Strin final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder); final CashierTransactionsWithSummaryData cashierTxnWithSummary = this.tellerManagementReadPlatformService .retrieveCashierTransactionsWithSummary(cashierId, false, fromDate, toDate, currencyCode, searchParameters); - if (cashierTxnWithSummary.getNetCash().subtract(transactionAmount).compareTo(BigDecimal.ZERO) < 0) { + if (MathUtil.isGreaterThan(transactionAmount, cashierTxnWithSummary.getNetCash())) { throw new CashierInsufficientAmountException(); } } @@ -81,16 +80,12 @@ public void validateCashierAllowedDateAndTime(final Cashier cashier, final Telle final LocalDate endDate = cashier.getEndDate(); final LocalDate tellerFromDate = teller.getStartDate(); final LocalDate tellerEndDate = teller.getEndDate(); - /** - * to validate cashier date range in range of teller date range - */ - if (fromDate.isBefore(tellerFromDate) || endDate.isBefore(tellerFromDate) - || (tellerEndDate != null && (fromDate.isAfter(tellerEndDate) || endDate.isAfter(tellerEndDate)))) { + // to validate cashier date range in range of teller date range + if (DateUtils.isBefore(fromDate, tellerFromDate) || DateUtils.isBefore(endDate, tellerFromDate) + || (tellerEndDate != null && (DateUtils.isAfter(fromDate, tellerEndDate) || DateUtils.isAfter(endDate, tellerEndDate)))) { throw new CashierDateRangeOutOfTellerDateRangeException(); } - /** - * to validate cashier has not been assigned for same duration - */ + // to validate cashier has not been assigned for same duration String sql = "select count(*) from m_cashiers c where c.staff_id = " + staffId + " AND " + "(('" + fromDate + "' BETWEEN c.start_date AND c.end_date OR '" + endDate + "' BETWEEN c.start_date AND c.end_date )" + " OR ( c.start_date BETWEEN '" + fromDate + "' AND '" + endDate + "' OR c.end_date BETWEEN '" + fromDate + "' AND '" @@ -98,8 +93,8 @@ public void validateCashierAllowedDateAndTime(final Cashier cashier, final Telle if (!cashier.getIsFullDay()) { String startTime = cashier.getStartTime(); String endTime = cashier.getEndTime(); - sql = sql + " AND ( Time(c.start_time) BETWEEN TIME(?) and TIME('" + endTime + "') or Time(c.end_time) BETWEEN TIME('" - + startTime + "') and TIME('" + endTime + "')) "; + sql = sql + " AND ( Time(c.start_time) BETWEEN TIME('" + startTime + "') and TIME('" + endTime + + "') or Time(c.end_time) BETWEEN TIME('" + startTime + "') and TIME('" + endTime + "')) "; } int count = this.jdbcTemplate.queryForObject(sql, Integer.class); // NOSONAR if (count > 0) { @@ -108,13 +103,13 @@ public void validateCashierAllowedDateAndTime(final Cashier cashier, final Telle } public void validateOnLoanDisbursal(AppUser user, String currencyCode, BigDecimal transactionAmount) { - LocalDateTime localDateTime = DateUtils.getLocalDateTimeOfTenant(); + LocalDate tenantDate = DateUtils.getLocalDateOfTenant(); + OffsetDateTime tenantDateTime = DateUtils.getOffsetDateTimeOfTenant(); if (user.getStaff() != null) { - String sql = "select c.id from m_cashiers c where c.staff_id = " + user.getStaff().getId() + " AND " - + " (case when c.full_day then '" + localDateTime.toLocalDate() + "' BETWEEN c.start_date AND c.end_date " + " else ('" - + localDateTime.toLocalDate() + "' BETWEEN c.start_date AND c.end_date and " + " TIME('" - + ZonedDateTime.of(localDateTime, DateUtils.getDateTimeZoneOfTenant()) - + "') BETWEEN TIME(c.start_time) AND TIME(c.end_time) ) end)"; + String sql = "select c.id from m_cashiers c where c.staff_id = " + user.getStaff().getId() + " AND (case when c.full_day then '" + + tenantDate + "' BETWEEN c.start_date AND c.end_date else ('" + tenantDate + + "' BETWEEN c.start_date AND c.end_date and TIME('" + tenantDateTime + + "') BETWEEN TIME(c.start_time) AND TIME(c.end_time)) end)"; try { Long cashierId = this.jdbcTemplate.queryForObject(sql, Long.class); // NOSONAR validateSettleCashAndCashOutTransactions(cashierId, currencyCode, transactionAmount); @@ -122,6 +117,5 @@ public void validateOnLoanDisbursal(AppUser user, String currencyCode, BigDecima LOG.error("Problem occurred in validateOnLoanDisbursal function", e); } } - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/serialization/TellerCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/serialization/TellerCommandFromApiJsonDeserializer.java index 0fccf5425c3..bf045239f5d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/serialization/TellerCommandFromApiJsonDeserializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/serialization/TellerCommandFromApiJsonDeserializer.java @@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.teller.exception.InvalidDateInputException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -113,7 +114,7 @@ public void validateForCreateAndUpdateTeller(final String json) { final String status = this.fromApiJsonHelper.extractStringNamed(STATUS, element); baseDataValidator.reset().parameter(STATUS).value(status).notBlank().notExceedingLengthOf(50); - if (endDate != null && endDate.isBefore(startDate)) { + if (endDate != null && DateUtils.isBefore(endDate, startDate)) { throw new InvalidDateInputException(startDate.toString(), endDate.toString()); } throwExceptionIfValidationWarningsExist(dataValidationErrors); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java index 0a40087b496..4effdf9817b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java @@ -56,10 +56,8 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -@Service @RequiredArgsConstructor public class TellerManagementReadPlatformServiceImpl implements TellerManagementReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java index 4a3068019d3..0150cfae71e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Set; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity; import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount; @@ -53,19 +54,14 @@ import org.apache.fineract.organisation.teller.exception.CashierNotFoundException; import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer; import org.apache.fineract.useradministration.domain.AppUser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @AllArgsConstructor -@Service +@Slf4j public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(TellerWritePlatformServiceJpaImpl.class); - private final PlatformSecurityContext context; private final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final TellerRepositoryWrapper tellerRepositoryWrapper; @@ -193,7 +189,7 @@ private void handleTellerDataIntegrityIssues(final JsonCommand command, final Th "name", name); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.teller.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/starter/OrganisationTellerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/starter/OrganisationTellerConfiguration.java new file mode 100644 index 00000000000..fab742fdba6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/starter/OrganisationTellerConfiguration.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.teller.starter; + +import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper; +import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; +import org.apache.fineract.organisation.staff.domain.StaffRepository; +import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; +import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator; +import org.apache.fineract.organisation.teller.domain.CashierRepository; +import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository; +import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper; +import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer; +import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformService; +import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformServiceImpl; +import org.apache.fineract.organisation.teller.service.TellerWritePlatformService; +import org.apache.fineract.organisation.teller.service.TellerWritePlatformServiceJpaImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class OrganisationTellerConfiguration { + + @Bean + @ConditionalOnMissingBean(TellerManagementReadPlatformService.class) + public TellerManagementReadPlatformService tellerManagementReadPlatformService(JdbcTemplate jdbcTemplate, + PlatformSecurityContext context, OfficeReadPlatformService officeReadPlatformService, + StaffReadPlatformService staffReadPlatformService, CurrencyReadPlatformService currencyReadPlatformService, + DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper, ColumnValidator columnValidator) { + return new TellerManagementReadPlatformServiceImpl(jdbcTemplate, context, officeReadPlatformService, staffReadPlatformService, + currencyReadPlatformService, sqlGenerator, paginationHelper, columnValidator); + } + + @Bean + @ConditionalOnMissingBean(TellerWritePlatformService.class) + public TellerWritePlatformService tellerWritePlatformService(PlatformSecurityContext context, + TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer, TellerRepositoryWrapper tellerRepositoryWrapper, + OfficeRepositoryWrapper officeRepositoryWrapper, StaffRepository staffRepository, CashierRepository cashierRepository, + CashierTransactionRepository cashierTxnRepository, JournalEntryRepository glJournalEntryRepository, + FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper, + CashierTransactionDataValidator cashierTransactionDataValidator) { + return new TellerWritePlatformServiceJpaImpl(context, fromApiJsonDeserializer, tellerRepositoryWrapper, officeRepositoryWrapper, + staffRepository, cashierRepository, cashierTxnRepository, glJournalEntryRepository, + financialActivityAccountRepositoryWrapper, cashierTransactionDataValidator); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java index 45653921ce5..59d6deeaa7d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java @@ -22,28 +22,22 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.organisation.workingdays.data.WorkingDaysData; import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysEnumerations; import org.apache.fineract.organisation.workingdays.exception.WorkingDaysNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class WorkingDaysReadPlatformServiceImpl implements WorkingDaysReadPlatformService { private final JdbcTemplate jdbcTemplate; - @Autowired - public WorkingDaysReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - private static final class WorkingDaysMapper implements RowMapper { private final String schema; diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java index 57700c7e3f9..b0611c28bfb 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java @@ -20,6 +20,7 @@ import java.text.ParseException; import java.util.Map; +import lombok.RequiredArgsConstructor; import net.fortuna.ical4j.model.property.RRule; import net.fortuna.ical4j.validate.ValidationException; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -30,23 +31,14 @@ import org.apache.fineract.organisation.workingdays.data.WorkingDayValidator; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class WorkingDaysWritePlatformServiceJpaRepositoryImpl implements WorkingDaysWritePlatformService { private final WorkingDaysRepositoryWrapper daysRepositoryWrapper; private final WorkingDayValidator fromApiJsonDeserializer; - @Autowired - public WorkingDaysWritePlatformServiceJpaRepositoryImpl(final WorkingDaysRepositoryWrapper daysRepositoryWrapper, - final WorkingDayValidator fromApiJsonDeserializer) { - this.daysRepositoryWrapper = daysRepositoryWrapper; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - } - @Transactional @Override public CommandProcessingResult updateWorkingDays(JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java new file mode 100644 index 00000000000..256909f5137 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.workingdays.starter; + +import org.apache.fineract.organisation.workingdays.data.WorkingDayValidator; +import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformService; +import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformServiceImpl; +import org.apache.fineract.organisation.workingdays.service.WorkingDaysWritePlatformService; +import org.apache.fineract.organisation.workingdays.service.WorkingDaysWritePlatformServiceJpaRepositoryImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class OrganisationWorkingDaysConfiguration { + + @Bean + @ConditionalOnMissingBean(WorkingDaysReadPlatformService.class) + public WorkingDaysReadPlatformService workingDaysReadPlatformService(JdbcTemplate jdbcTemplate) { + return new WorkingDaysReadPlatformServiceImpl(jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(WorkingDaysWritePlatformService.class) + public WorkingDaysWritePlatformService workingDaysWritePlatformService(WorkingDaysRepositoryWrapper daysRepositoryWrapper, + WorkingDayValidator fromApiJsonDeserializer) { + return new WorkingDaysWritePlatformServiceJpaRepositoryImpl(daysRepositoryWrapper, fromApiJsonDeserializer); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResourceSwagger.java index 8757da3521d..8e34eff52a9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResourceSwagger.java @@ -39,13 +39,13 @@ static final class GetAccountTransfersFromOffice { private GetAccountTransfersFromOffice() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "HO") public String name; @Schema(example = "HO") public String nameDecorated; @Schema(example = "1") - public Integer externalId; + public String externalId; @Schema(example = "[2009, 1, 1]") public LocalDate openingDate; @Schema(example = ".") @@ -57,7 +57,7 @@ static final class GetAccountTransfersFromAccountType { private GetAccountTransfersFromAccountType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountType.savings") public String code; @Schema(example = "Savings Account") @@ -69,7 +69,7 @@ static final class GetAccountTransfersFromOfficeOptions { private GetAccountTransfersFromOfficeOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "HO") public String name; @Schema(example = "HO") @@ -81,11 +81,11 @@ static final class GetAccountTransfersFromClientOptions { private GetAccountTransfersFromClientOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Small shop") public String displayName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "HO") public String officeName; } @@ -95,7 +95,7 @@ static final class GetAccountTransfersFromAccountTypeOptions { private GetAccountTransfersFromAccountTypeOptions() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountType.savings") public String code; @Schema(example = "Savings Account") @@ -107,7 +107,7 @@ static final class GetAccountTransfersToOfficeOptions { private GetAccountTransfersToOfficeOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "HO") public String name; @Schema(example = "HO") @@ -119,7 +119,7 @@ static final class GetAccountTransfersToAccountTypeOptions { private GetAccountTransfersToAccountTypeOptions() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountType.savings") public String code; @Schema(example = "Savings Account") @@ -145,21 +145,21 @@ public static final class PostAccountTransfersRequest { private PostAccountTransfersRequest() {} @Schema(example = "1") - public Integer fromOfficeId; + public Long fromOfficeId; @Schema(example = "1") - public Integer fromClientId; + public Long fromClientId; @Schema(example = "2") public Integer fromAccountType; @Schema(example = "1") - public Integer fromAccountId; + public Long fromAccountId; @Schema(example = "1") - public Integer toOfficeId; + public Long toOfficeId; @Schema(example = "1") - public Integer toClientId; + public Long toClientId; @Schema(example = "2") public Integer toAccountType; @Schema(example = "2") - public Integer toAccountId; + public Long toAccountId; @Schema(example = "dd MMMM yyyy") public String dateFormat; @Schema(example = "en") @@ -178,9 +178,9 @@ public static final class PostAccountTransfersResponse { private PostAccountTransfersResponse() {} @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "GetAccountTransfersResponse") @@ -215,7 +215,7 @@ static final class GetAccountTransfersPageItemsFromOffice { private GetAccountTransfersPageItemsFromOffice() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "HO") public String name; } @@ -225,7 +225,7 @@ static final class GetAccountTransfersPageItemsFromAccount { private GetAccountTransfersPageItemsFromAccount() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "000000001") public Long accountNo; } @@ -235,7 +235,7 @@ static final class GetAccountTransfersPageItemsToAccountType { private GetAccountTransfersPageItemsToAccountType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "accountType.loan") public String code; @Schema(example = "Loan Account") @@ -243,7 +243,7 @@ private GetAccountTransfersPageItemsToAccountType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "false") public Boolean reversed; public GetAccountTransfersPageItemsCurrency currency; @@ -298,13 +298,13 @@ static final class GetAccountTransfersTemplateRefundByTransferFromOffice { private GetAccountTransfersTemplateRefundByTransferFromOffice() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Head Office") public String name; @Schema(example = "Head Office") public String nameDecorated; @Schema(example = "1") - public Integer externalId; + public String externalId; @Schema(example = "[2009, 1, 1]") public LocalDate openingDate; @Schema(example = ".") @@ -320,7 +320,7 @@ static final class GetAccountTransfersStatus { private GetAccountTransfersStatus() {} @Schema(example = "300") - public Integer id; + public Long id; @Schema(example = "clientStatusType.active") public String code; @Schema(example = "Active") @@ -370,7 +370,7 @@ private GetAccountTransfersGroups() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "000000001") public Long accountNo; public GetAccountTransfersStatus status; @@ -388,7 +388,7 @@ private GetAccountTransfersGroups() {} public GetAccountTransfersClientType clientType; public GetAccountTransfersClientClassification clientClassification; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; public GetAccountTransfersTimeline timeline; @@ -400,19 +400,19 @@ static final class GetAccountTransfersTemplateRefundByTransferFromAccount { private GetAccountTransfersTemplateRefundByTransferFromAccount() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "000000002") public Long accountNo; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "Daniel Owusu") public String clientName; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "CTRL") public String productName; @Schema(example = "0") - public Integer fieldOfficerId; + public Long fieldOfficerId; public GetAccountTransfersTemplateRefundByTransferCurrency currency; @Schema(example = "130") public Float amtForTransfer; @@ -423,11 +423,11 @@ static final class GetAccountTransfersTemplateRefundByTransferToClient { private GetAccountTransfersTemplateRefundByTransferToClient() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Daniel Owusu") public String displayName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; } @@ -437,19 +437,19 @@ static final class GetAccountTransfersTemplateRefundByTransferToAccount { private GetAccountTransfersTemplateRefundByTransferToAccount() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "000000001") public Long accountNo; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "Daniel Owusu") public String clientName; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "TEST") public String productName; @Schema(example = "0") - public Integer fieldOfficerId; + public Long fieldOfficerId; public GetAccountTransfersTemplateRefundByTransferCurrency currency; } @@ -458,7 +458,7 @@ static final class GetAccountTransfersTemplateRefundByTransferFromOfficeOptions private GetAccountTransfersTemplateRefundByTransferFromOfficeOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Head Office") public String name; @Schema(example = "Head Office") @@ -470,11 +470,11 @@ static final class GetAccountTransfersTemplateRefundByTransferFromClientOptions private GetAccountTransfersTemplateRefundByTransferFromClientOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Daniel Owusu") public String displayName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; } @@ -484,19 +484,19 @@ static final class GetAccountTransfersTemplateRefundByTransferFromAccountOptions private GetAccountTransfersTemplateRefundByTransferFromAccountOptions() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "000000002") public Long accountNo; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "Daniel Owusu") public String clientName; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "CTRL") public String productName; @Schema(example = "0") - public Integer fieldOfficerId; + public Long fieldOfficerId; public GetAccountTransfersTemplateRefundByTransferCurrency currency; } @@ -529,17 +529,17 @@ public static final class PostAccountTransfersRefundByTransferRequest { private PostAccountTransfersRefundByTransferRequest() {} @Schema(example = "2") - public Integer fromAccountId; + public Long fromAccountId; @Schema(example = "1") public Integer fromAccountType; @Schema(example = "1") - public Integer toOfficeId; + public Long toOfficeId; @Schema(example = "1") - public Integer toClientId; + public Long toClientId; @Schema(example = "2") public Integer toAccountType; @Schema(example = "1") - public Integer toAccountId; + public Long toAccountId; @Schema(example = "130") public Float transferAmount; @Schema(example = "31 October 2014") @@ -551,9 +551,9 @@ private PostAccountTransfersRefundByTransferRequest() {} @Schema(example = "dd MMMM yyyy") public String dateFormat; @Schema(example = "1") - public Integer fromClientId; + public Long fromClientId; @Schema(example = "1") - public Integer fromOfficeId; + public Long fromOfficeId; } @Schema(description = "PostAccountTransfersRefundByTransferResponse") @@ -562,8 +562,8 @@ public static final class PostAccountTransfersRefundByTransferResponse { private PostAccountTransfersRefundByTransferResponse() {} @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResourceSwagger.java index 207d9453173..b2bc885b4ac 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResourceSwagger.java @@ -47,7 +47,7 @@ private GetFromOfficeResponseStandingInstructionSwagger() {} @Schema(example = "Head Office") public String decoratedName; @Schema(example = "1") - public Integer externalId; + public String externalId; @Schema(example = "[2009, 1, 1]") public LocalDate openingDate; @Schema(example = ".") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java index 16fd0c3e191..888ba2d92ca 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java @@ -75,12 +75,12 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon LocalDate startDate = data.validFrom(); if (frequencyType.isMonthly()) { startDate = startDate.withDayOfMonth(data.recurrenceOnDay()); - if (startDate.isBefore(data.validFrom())) { + if (DateUtils.isBefore(startDate, data.validFrom())) { startDate = startDate.plusMonths(1); } } else if (frequencyType.isYearly()) { startDate = startDate.withDayOfMonth(data.recurrenceOnDay()).withMonth(data.recurrenceOnMonth()); - if (startDate.isBefore(data.validFrom())) { + if (DateUtils.isBefore(startDate, data.validFrom())) { startDate = startDate.plusYears(1); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index dd07013f34a..ca2305c8de4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -536,8 +536,7 @@ public CommandProcessingResult refundByTransfer(JsonCommand command) { BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(fromLoanAccountId).getPaidInAdvance(); final boolean backdatedTxnsAllowedTill = false; - if (overpaid == null || overpaid.compareTo(BigDecimal.ZERO) == 0 ? Boolean.TRUE - : Boolean.FALSE || transactionAmount.floatValue() > overpaid.floatValue()) { + if (overpaid == null || overpaid.compareTo(BigDecimal.ZERO) == 0 || transactionAmount.floatValue() > overpaid.floatValue()) { if (overpaid == null) { overpaid = BigDecimal.ZERO; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/api/AccountsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/api/AccountsApiResourceSwagger.java index 8be974be8f2..049f2191059 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/api/AccountsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/api/AccountsApiResourceSwagger.java @@ -40,7 +40,7 @@ static final class GetAccountsTypeProductOptions { private GetAccountsTypeProductOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Share Product") public String name; @Schema(example = "SP") @@ -50,7 +50,7 @@ private GetAccountsTypeProductOptions() {} } @Schema(example = "7") - public Integer clientId; + public Long clientId; @Schema(example = "Client Name") public String clientName; public Set productOptions; @@ -66,7 +66,7 @@ static final class GetAccountsStatus { private GetAccountsStatus() {} @Schema(example = "300") - public Integer id; + public Long id; @Schema(example = "shareAccountStatusType.active") public String code; @Schema(example = "Active") @@ -132,7 +132,7 @@ static final class GetAccountsSummary { private GetAccountsSummary() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "000000002") public Long accountNo; @Schema(example = "1") @@ -140,7 +140,7 @@ private GetAccountsSummary() {} @Schema(example = "0") public Integer totalPendingForApprovalShares; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "Conflux Share Product") public String productName; public GetAccountsStatus status; @@ -157,7 +157,7 @@ static final class GetAccountsPurchasedSharesStatus { private GetAccountsPurchasedSharesStatus() {} @Schema(example = "300") - public Integer id; + public Long id; @Schema(example = "purchasedSharesStatusType.approved") public String code; @Schema(example = "Approved") @@ -169,7 +169,7 @@ static final class GetAccountsPurchasedSharesType { private GetAccountsPurchasedSharesType() {} @Schema(example = "500") - public Integer id; + public Long id; @Schema(example = "purchasedSharesType.purchased") public String code; @Schema(example = "Purchase") @@ -177,9 +177,9 @@ private GetAccountsPurchasedSharesType() {} } @Schema(example = "6") - public Integer id; + public Long id; @Schema(example = "2") - public Integer accountId; + public Long accountId; @Schema(example = "[2016, 4, 1]") public LocalDate purchasedDate; @Schema(example = "10") @@ -201,7 +201,7 @@ static final class GetAccountsLockPeriodTypeEnum { private GetAccountsLockPeriodTypeEnum() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "savings.lockin.sharePeriodFrequencyType.days") public String code; @Schema(example = "Days") @@ -217,7 +217,7 @@ static final class GetAccountsChargeTimeType { private GetAccountsChargeTimeType() {} @Schema(example = "13") - public Integer id; + public Long id; @Schema(example = "chargeTimeType.activation") public String code; @Schema(example = "Share Account Activate") @@ -229,7 +229,7 @@ static final class GetAccountsChargeCalculationType { private GetAccountsChargeCalculationType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "chargeCalculationType.flat") public String code; @Schema(example = "Flat") @@ -255,11 +255,11 @@ private GetAccountsChargesCurrency() {} } @Schema(example = "9") - public Integer id; + public Long id; @Schema(example = "20") - public Integer chargeId; + public Long chargeId; @Schema(example = "2") - public Integer accountId; + public Long accountId; @Schema(example = "Share Account Activation Flat") public String name; public GetAccountsChargeTimeType chargeTimeType; @@ -286,17 +286,17 @@ private GetAccountsChargesCurrency() {} } @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "000000002") public Long accountNo; @Schema(example = "000000013") public Long savingsAccountNumber; @Schema(example = "7") - public Integer clientId; + public Long clientId; @Schema(example = "Client_FirstName_2KX8C Client_LastName_NWNG") public String clientName; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "Share Product") public String productName; public GetAccountsStatus status; @@ -305,7 +305,7 @@ private GetAccountsChargesCurrency() {} public GetAccountsSummary summary; public Set purchasedShares; @Schema(example = "13") - public Integer savingsAccountId; + public Long savingsAccountId; @Schema(example = "5") public Integer currentMarketPrice; @Schema(example = "1") @@ -335,7 +335,7 @@ static final class GetAccountsTypeStatus { private GetAccountsTypeStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "shareAccountStatusType.submitted.and.pending.approval") public String code; @Schema(example = "Submitted and pending approval") @@ -365,7 +365,7 @@ static final class GetAccountsTypePurchasedShares { private GetAccountsTypePurchasedShares() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "01 May 2013") public String purchasedDate; @Schema(example = "10") @@ -382,15 +382,15 @@ private GetAccountsTypeSummary() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "000000001") public Long accountNo; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "Client Name") public String clientName; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "Share Product Name") public String productName; public GetAccountsTypeStatus status; @@ -415,19 +415,19 @@ static final class PostAccountsCharges { private PostAccountsCharges() {} @Schema(example = "20") - public Integer chargeId; + public Long chargeId; @Schema(example = "1") public Integer amount; } @Schema(example = "7") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "100") public Integer requestedShares; @Schema(example = "1") - public Integer externalId; + public String externalId; @Schema(example = "01 May 2016") public String submittedDate; @Schema(example = "1") @@ -448,7 +448,7 @@ private PostAccountsCharges() {} public String dateFormat; public Set charges; @Schema(example = "13") - public Integer savingsAccountId; + public Long savingsAccountId; } @Schema(description = "PostAccountsTypeResponse") @@ -457,7 +457,7 @@ public static final class PostAccountsTypeResponse { private PostAccountsTypeResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PostAccountsTypeAccountIdRequest") @@ -470,7 +470,7 @@ static final class PostAccountsRequestedShares { private PostAccountsRequestedShares() {} @Schema(example = "35") - public Integer id; + public Long id; } public Set requestedShares; @@ -482,7 +482,7 @@ public static final class PostAccountsTypeAccountIdResponse { private PostAccountsTypeAccountIdResponse() {} @Schema(example = "5") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutAccountsTypeAccountIdRequest") @@ -520,7 +520,7 @@ private PutAccountsChanges() {} } @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PutAccountsChanges changes; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/api/EntityFieldConfigurationApiResourcesSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/api/EntityFieldConfigurationApiResourcesSwagger.java index 8efea61fcf3..73556ee52d8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/api/EntityFieldConfigurationApiResourcesSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/api/EntityFieldConfigurationApiResourcesSwagger.java @@ -34,7 +34,7 @@ public static final class GetFieldConfigurationEntityResponse { private GetFieldConfigurationEntityResponse() {} @Schema(example = "1") - public Integer fieldConfigurationId; + public Long fieldConfigurationId; @Schema(example = "ADDRESS") public String entity; @Schema(example = "CLIENT") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java index c731dc2fc01..22bee0c5542 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java @@ -279,7 +279,7 @@ private LocalDate getPeriodStartDate(final LocalDate seedDate, final LocalDate r periodStartDate = fromDate; } else { final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); - if (seedDate.isBefore(currentDate.minusYears(1))) { + if (DateUtils.isBefore(seedDate, currentDate.minusYears(1))) { periodStartDate = currentDate.minusYears(1); } else { periodStartDate = recurrenceStartDate; @@ -294,7 +294,7 @@ private LocalDate getPeriodEndDate(LocalDate endDate, LocalDate tillDate) { if (tillDate != null) { if (endDate != null) { - if (endDate.isAfter(tillDate)) { + if (DateUtils.isAfter(endDate, tillDate)) { // to retrieve meeting dates tillspecified date (tillDate) periodEndDate = tillDate; } @@ -302,7 +302,7 @@ private LocalDate getPeriodEndDate(LocalDate endDate, LocalDate tillDate) { // end date is null then fetch meeting dates tillDate periodEndDate = tillDate; } - } else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) { + } else if (endDate == null || DateUtils.isAfter(endDate, currentDate.plusYears(1))) { periodEndDate = currentDate.plusYears(1); } return periodEndDate; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java index 51e998ec765..a61ac96380f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java @@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.calendar.CalendarConstants.CalendarSupportedParameters; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; @@ -78,7 +79,7 @@ public CommandProcessingResult createCalendar(final JsonCommand command) { Group centerOrGroup = null; if (command.getGroupId() != null) { centerOrGroup = this.groupRepository.findOneWithNotFoundDetection(command.getGroupId()); - entityActivationDate = centerOrGroup.getActivationLocalDate(); + entityActivationDate = centerOrGroup.getActivationDate(); entityType = centerOrGroup.isCenter() ? CalendarEntityType.CENTERS : CalendarEntityType.GROUPS; entityId = command.getGroupId(); } else if (command.getLoanId() != null) { @@ -88,7 +89,7 @@ public CommandProcessingResult createCalendar(final JsonCommand command) { entityId = command.getLoanId(); } else if (command.getClientId() != null) { final Client client = this.clientRepository.findOneWithNotFoundDetection(command.getClientId()); - entityActivationDate = client.getActivationLocalDate(); + entityActivationDate = client.getActivationDate(); entityType = CalendarEntityType.CLIENTS; entityId = command.getClientId(); } @@ -98,7 +99,7 @@ public CommandProcessingResult createCalendar(final JsonCommand command) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("calendar"); - if (entityActivationDate == null || newCalendar.getStartDateLocalDate().isBefore(entityActivationDate)) { + if (entityActivationDate == null || DateUtils.isBefore(newCalendar.getStartDateLocalDate(), entityActivationDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); String dateAsString = ""; if (entityActivationDate != null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientAddressApiResourcesSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientAddressApiResourcesSwagger.java index 177fcd2722d..5b557fc0a4e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientAddressApiResourcesSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientAddressApiResourcesSwagger.java @@ -44,9 +44,9 @@ private PostClientClientIdAddressesRequest() {} @Schema(example = "Mumbai") public String city; @Schema(example = "800") - public Integer stateProvinceId; + public Long stateProvinceId; @Schema(example = "802") - public Integer countryId; + public Long countryId; @Schema(example = "400064") public Long postalCode; @Schema(example = "true") @@ -59,7 +59,7 @@ public static final class PostClientClientIdAddressesResponse { private PostClientClientIdAddressesResponse() {} @Schema(example = "15") - public Integer resourceId; + public Long resourceId; } @Schema(description = "GetClientClientIdAddressesResponse") @@ -72,9 +72,9 @@ private GetClientClientIdAddressesResponse() {} @Schema(example = "PERMANENT ADDRESS") public String addressType; @Schema(example = "14") - public Integer addressId; + public Long addressId; @Schema(example = "804") - public Integer addressTypeId; + public Long addressTypeId; @Schema(example = "false") public Boolean isActive; @Schema(example = "anki's home") @@ -92,13 +92,13 @@ private GetClientClientIdAddressesResponse() {} @Schema(example = " ") public String countyDistrict; @Schema(example = "801") - public Integer stateProvinceId; + public Long stateProvinceId; @Schema(example = "UNITED STATES") public String countryName; @Schema(example = "GUJRAT") public String stateName; @Schema(example = "807") - public Integer countryId; + public Long countryId; @Schema(example = "400095") public Long postalCode; @Schema(example = " ") @@ -113,7 +113,7 @@ public static final class PutClientClientIdAddressesRequest { private PutClientClientIdAddressesRequest() {} @Schema(example = "67") - public Integer addressId; + public Long addressId; @Schema(example = "goldensource") public String street; } @@ -124,6 +124,6 @@ public static final class PutClientClientIdAddressesResponse { private PutClientClientIdAddressesResponse() {} @Schema(example = "67") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResourceSwagger.java index 0769c0b38b2..a2d8d57c6e4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResourceSwagger.java @@ -82,11 +82,11 @@ private GetClientChargeCurrency() {} } @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "5") - public Integer chargeId; + public Long chargeId; @Schema(example = "Client Fee 1") public String name; public GetClientChargeTimeType chargeTimeType; @@ -127,7 +127,7 @@ private PostClientsClientIdChargesRequest() {} @Schema(example = "100") public Integer amount; @Schema(example = "226") - public Integer chargeId; + public Long chargeId; @Schema(example = "dd MMMM yyyy") public String dateFormat; @Schema(example = "01 September 2015") @@ -142,11 +142,11 @@ public static final class PostClientsClientIdChargesResponse { private PostClientsClientIdChargesResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "189") - public Integer chargeId; + public Long chargeId; @Schema(example = "164") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PostClientsClientIdChargesChargeIdRequest") @@ -172,13 +172,13 @@ public static final class PostClientsClientIdChargesChargeIdResponse { private PostClientsClientIdChargesChargeIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "189") - public Integer clientId; + public Long clientId; @Schema(example = "157") - public Integer resourceId; + public Long resourceId; @Schema(example = "221") - public Integer transactionId; + public Long transactionId; } @Schema(description = "DeleteClientsClientIdChargesChargeIdResponse") @@ -187,10 +187,10 @@ public static final class DeleteClientsClientIdChargesChargeIdResponse { private DeleteClientsClientIdChargesChargeIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "189") - public Integer clientId; + public Long clientId; @Schema(example = "164") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientIdentifiersApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientIdentifiersApiResourceSwagger.java index 7db6ed6c061..fb69d6f2f4d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientIdentifiersApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientIdentifiersApiResourceSwagger.java @@ -38,15 +38,15 @@ static final class GetClientsDocumentType { private GetClientsDocumentType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "Drivers License") public String name; } @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "1") - public Integer clientId; + public Long clientId; public GetClientsDocumentType documentType; @Schema(example = "12345") public String documentKey; @@ -64,7 +64,7 @@ static final class GetClientsAllowedDocumentTypes { private GetClientsAllowedDocumentTypes() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Passport") public String name; @Schema(example = "0") @@ -80,7 +80,7 @@ public static final class PostClientsClientIdIdentifiersRequest { private PostClientsClientIdIdentifiersRequest() {} @Schema(example = "1") - public Integer documentTypeId; + public Long documentTypeId; @Schema(example = "KA-54677") public String documentKey; @Schema(example = "Document has been verified") @@ -93,7 +93,7 @@ public static final class PutClientsClientIdIdentifiersIdentifierIdRequest { private PutClientsClientIdIdentifiersIdentifierIdRequest() {} @Schema(example = "4") - public Integer documentTypeId; + public Long documentTypeId; @Schema(example = "KA-94667") public String documentKey; @Schema(example = "Document has been updated") @@ -106,11 +106,11 @@ public static final class PutClientsClientIdIdentifiersIdentifierIdResponse { private PutClientsClientIdIdentifiersIdentifierIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "3") - public Integer resourceId; + public Long resourceId; public PutClientsClientIdIdentifiersIdentifierIdRequest changes; } @@ -120,11 +120,11 @@ public static final class PostClientsClientIdIdentifiersResponse { private PostClientsClientIdIdentifiersResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "3") - public Integer resourceId; + public Long resourceId; } @Schema(description = "DeleteClientsClientIdIdentifiersIdentifierIdResponse") @@ -133,10 +133,10 @@ public static final class DeleteClientsClientIdIdentifiersIdentifierIdResponse { private DeleteClientsClientIdIdentifiersIdentifierIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "3") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResourceSwagger.java index 6c8c74a3ce8..9864f7215c9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResourceSwagger.java @@ -44,7 +44,7 @@ static final class GetClientsClientIdTransactionsType { private GetClientsClientIdTransactionsType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "clientTransactionType.payCharge") public String code; @Schema(example = "PAY_CHARGE") @@ -70,9 +70,9 @@ private GetClientTransactionsCurrency() {} } @Schema(example = "226") - public Integer id; + public Long id; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; public GetClientsClientIdTransactionsType type; @@ -98,9 +98,9 @@ public static final class GetClientsClientIdTransactionsTransactionIdResponse { private GetClientsClientIdTransactionsTransactionIdResponse() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; public GetClientsClientIdTransactionsResponse.GetClientsPageItems.GetClientsClientIdTransactionsType type; @@ -121,10 +121,10 @@ public static final class PostClientsClientIdTransactionsTransactionIdResponse { private PostClientsClientIdTransactionsTransactionIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "189") - public Integer clientId; + public Long clientId; @Schema(example = "222") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java index 017837f4092..bf5e6ddb300 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java @@ -41,7 +41,7 @@ static final class GetClientsOfficeOptions { private GetClientsOfficeOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Head Office") public String name; @Schema(example = "Head Office") @@ -53,7 +53,7 @@ static final class GetClientsStaffOptions { private GetClientsStaffOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "xyz") public String firstname; @Schema(example = "sjs") @@ -61,7 +61,7 @@ private GetClientsStaffOptions() {} @Schema(example = "sjs, xyz") public String displayName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; @Schema(example = "true") @@ -75,7 +75,7 @@ static final class GetClientsSavingProductOptions { private GetClientsSavingProductOptions() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "account overdraft") public String name; @Schema(example = "false") @@ -118,7 +118,7 @@ private GetClientsColumnHeaderData() {} @Schema(example = "[2014, 3, 4]") public LocalDate activationDate; @Schema(example = "1") - public Integer officeId; + public Long officeId; public Set officeOptions; public Set staffOptions; public Set savingProductOptions; @@ -139,7 +139,7 @@ static final class GetClientStatus { private GetClientStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "clientStatusType.pending") public String code; @Schema(example = "Pending") @@ -158,7 +158,7 @@ private GetClientStatus() {} @Schema(example = "Home Farm Produce") public String displayName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; @Schema(example = "test@test.com") @@ -180,7 +180,7 @@ static final class GetClientsClientIdStatus { private GetClientsClientIdStatus() {} @Schema(example = "300") - public Integer id; + public Long id; @Schema(example = "clientStatusType.active") public String code; @Schema(example = "Active") @@ -225,7 +225,7 @@ private GetClientsGroups() {} } @Schema(example = "27") - public Integer id; + public Long id; @Schema(example = "000000027") public String accountNo; public GetClientsClientIdStatus status; @@ -240,12 +240,12 @@ private GetClientsGroups() {} @Schema(example = "savings test") public String displayName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; public GetClientsTimeline timeline; @Schema(example = "4") - public Integer savingsProductId; + public Long savingsProductId; @Schema(example = "account overdraft") public String savingsProductName; @Schema(example = "[]") @@ -285,9 +285,9 @@ static final class PostClientsAddressRequest { @Schema(example = "Mumbai") public String city; @Schema(example = "800") - public Integer stateProvinceId; + public Long stateProvinceId; @Schema(example = "802") - public Integer countryId; + public Long countryId; @Schema(example = "400064") public Long postalCode; @Schema(example = "1") @@ -297,13 +297,15 @@ static final class PostClientsAddressRequest { } @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer legalFormId; + public Long legalFormId; @Schema(example = "Client of group") public String fullname; @Schema(example = "Client_FirstName") public String firstname; + @Schema(example = "Client_MiddleName") + public String middlename; @Schema(example = "123") public String externalId; @Schema(example = "Client_LastName") @@ -311,7 +313,7 @@ static final class PostClientsAddressRequest { @Schema(example = "[2013, 1, 1]") public LocalDate dateOfBirth; @Schema(example = "1") - public Integer groupId; + public Long groupId; @Schema(example = "dd MMMM yyyy") public String dateFormat; @Schema(example = "en") @@ -336,13 +338,13 @@ public static final class PostClientsResponse { private PostClientsResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer groupId; + public Long groupId; @Schema(example = "2") public Long clientId; @Schema(example = "2") - public Integer resourceId; + public Long resourceId; @Schema(example = "123-456") public String resourceExternalId; } @@ -364,11 +366,11 @@ public static final class PutClientsClientIdResponse { private PutClientsClientIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; @Schema(example = "123-456") public String resourceExternalId; public PutClientsClientIdRequest changes; @@ -386,11 +388,11 @@ public static final class DeleteClientsClientIdResponse { private DeleteClientsClientIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "3") - public Integer clientId; + public Long clientId; @Schema(example = "3") - public Integer resourceId; + public Long resourceId; @Schema(example = "123-456") public String resourceExternalId; } @@ -414,11 +416,11 @@ public static final class PostClientsClientIdResponse { private PostClientsClientIdResponse() {} @Schema(example = "2") - public Integer officeId; + public Long officeId; @Schema(example = "2") - public Integer clientId; + public Long clientId; @Schema(example = "2") - public Integer resourceId; + public Long resourceId; @Schema(example = "123-456") public String resourceExternalId; } @@ -473,7 +475,7 @@ static final class GetClientsLoanAccountsStatus { private GetClientsLoanAccountsStatus() {} @Schema(example = "300") - public Integer id; + public Long id; @Schema(example = "loanStatusType.active") public String code; @Schema(example = "Active") @@ -501,7 +503,7 @@ static final class GetClientsLoanAccountsType { private GetClientsLoanAccountsType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "loanType.individual") public String code; @Schema(example = "Individual") @@ -509,13 +511,13 @@ private GetClientsLoanAccountsType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "000000001") public String accountNo; @Schema(example = "456") - public Integer externalId; + public String externalId; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "TestOne") public String productName; public GetClientsLoanAccountsStatus status; @@ -551,7 +553,7 @@ static final class GetClientsSavingsAccountsStatus { private GetClientsSavingsAccountsStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "savingsAccountStatusType.submitted.and.pending.approval") public String code; @Schema(example = "Submitted and pending approval") @@ -583,7 +585,7 @@ static final class GetClientsSavingsAccountsDepositType { private GetClientsSavingsAccountsDepositType() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "depositAccountType.savingsDeposit") public String code; @Schema(example = "Savings") @@ -591,11 +593,11 @@ private GetClientsSavingsAccountsDepositType() {} } @Schema(example = "7") - public Integer id; + public Long id; @Schema(example = "000000007") public String accountNo; @Schema(example = "2") - public Integer productId; + public Long productId; @Schema(example = "Other product") public String productName; @Schema(example = "OP") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java index e07c8f2d312..6c8f77000b2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java @@ -80,8 +80,8 @@ public String getClientAuditFields(@Context final UriInfo uriInfo, @PathParam("c final Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId); Map auditFields = new HashMap<>( - Map.of(CREATED_BY, client.getCreatedBy().orElse(null), CREATED_DATE, client.getCreatedDate().orElse(null), LAST_MODIFIED_BY, - client.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, client.getLastModifiedDate().orElse(null))); + Map.of(CREATED_BY, client.getCreatedBy().orElse(null), CREATED_DATE, client.getCreatedDateTime(), LAST_MODIFIED_BY, + client.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, client.getLastModifiedDateTime())); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, auditFields); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java index bbbe4ee7ed4..f8a5b927439 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java @@ -318,7 +318,7 @@ private void validateRequiredIndividualNamePartsExist(final JsonElement element, private void fullnameCannotBeBlank(final JsonElement element, final DataValidatorBuilder baseDataValidator) { final String fullnameParam = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.fullnameParamName, element); - baseDataValidator.reset().parameter(ClientApiConstants.fullnameParamName).value(fullnameParam).notBlank().notExceedingLengthOf(100); + baseDataValidator.reset().parameter(ClientApiConstants.fullnameParamName).value(fullnameParam).notBlank().notExceedingLengthOf(160); } private boolean isIndividualNamePartParameterPassed(final JsonElement element) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPerson.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPerson.java index 77dc21e29cd..b4367a3bb85 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPerson.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientNonPerson.java @@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.client.api.ClientApiConstants; @Entity @@ -116,8 +117,7 @@ private void validate(final Client client) { private void validateIncorpValidityTillDate(final Client client, final List dataValidationErrors) { if (getIncorpValidityTillLocalDate() != null && client.dateOfBirthLocalDate() != null - && client.dateOfBirthLocalDate().isAfter(getIncorpValidityTillLocalDate())) { - + && DateUtils.isAfter(client.dateOfBirthLocalDate(), getIncorpValidityTillLocalDate())) { final String defaultUserMessage = "incorpvaliditytill date cannot be after the incorporation date"; final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.incorpValidityTill.after.incorp.date", defaultUserMessage, ClientApiConstants.incorpValidityTillParamName, this.incorpValidityTill); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java index e6db6660660..f3a9ff11218 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java @@ -40,7 +40,8 @@ public interface ClientRepository extends JpaRepository, JpaSpecif WHERE client.id = :clientId AND (office.hierarchy LIKE :officeHierarchy OR transferToOffice.hierarchy LIKE :transferToOfficeHierarchy) """) - Client fetchByClientIdAndHierarchy(Long clientId, String officeHierarchy, String transferToOfficeHierarchy); + Client fetchByClientIdAndHierarchy(@Param("clientId") Long clientId, @Param("officeHierarchy") String officeHierarchy, + @Param("transferToOfficeHierarchy") String transferToOfficeHierarchy); @Query("SELECT c.id FROM Client c WHERE c.externalId = :externalId") Long findIdByExternalId(@Param("externalId") ExternalId externalId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformServiceImpl.java index baa51baa85f..38684f1da0e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeWritePlatformServiceImpl.java @@ -99,9 +99,9 @@ public CommandProcessingResult addCharge(Long clientId, JsonCommand command) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME); - LocalDate activationDate = client.getActivationLocalDate(); + LocalDate activationDate = client.getActivationDate(); LocalDate dueDate = clientCharge.getDueLocalDate(); - if (dueDate.isBefore(activationDate)) { + if (DateUtils.isBefore(dueDate, activationDate)) { baseDataValidator.reset().parameter(ClientApiConstants.dueAsOfDateParamName).value(dueDate.format(fmt)) .failWithCodeNoParameterAddedToErrorCode("dueDate.before.activationDate"); @@ -275,7 +275,7 @@ private void validatePaymentDateAndAmount(final Client client, final ClientCharg if (requiresTransactionDateValidation) { validateTransactionDateOnWorkingDay(transactionDate, clientCharge, fmt); - if (client.getActivationLocalDate() != null && transactionDate.isBefore(client.getActivationLocalDate())) { + if (DateUtils.isBefore(transactionDate, client.getActivationDate())) { baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName).value(transactionDate.format(fmt)) .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); throw new PlatformApiDataValidationException(dataValidationErrors); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformServiceJpaRepositoryImpl.java index 33ebf3b4ae9..96d60f44423 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientIdentifierWritePlatformServiceJpaRepositoryImpl.java @@ -164,7 +164,7 @@ public CommandProcessingResult updateClientIdentifier(final Long clientId, final .build(); } catch (final JpaSystemException | DataIntegrityViolationException dve) { handleClientIdentifierDataIntegrityViolation(documentTypeLabel, documentTypeId, documentKey, dve.getMostSpecificCause(), dve); - return new CommandProcessingResult(Long.valueOf(-1)); + return CommandProcessingResult.resourceResult(-1L); } catch (final PersistenceException dve) { Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); handleClientIdentifierDataIntegrityViolation(documentTypeLabel, documentTypeId, documentKey, throwable, dve); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java index 9b8b4a7fd92..3a1013a550e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java @@ -277,7 +277,7 @@ public CommandProcessingResult createClient(final JsonCommand command) { if (command.hasParameter(ClientApiConstants.submittedOnDateParamName)) { submittedOnDate = command.localDateValueOfParameterNamed(ClientApiConstants.submittedOnDateParamName); } - if (active && submittedOnDate.isAfter(activationDate)) { + if (active && DateUtils.isAfter(submittedOnDate, activationDate)) { submittedOnDate = activationDate; } final Long savingsAccountId = null; @@ -743,7 +743,7 @@ private CommandProcessingResult openSavingsAccount(final Client client, final Da CommandProcessingResult commandProcessingResult = CommandProcessingResult.empty(); if (client.isActive() && client.savingsProductId() != null) { SavingsAccountDataDTO savingsAccountDataDTO = new SavingsAccountDataDTO(client, null, client.savingsProductId(), - client.getActivationLocalDate(), client.activatedBy(), fmt); + client.getActivationDate(), client.activatedBy(), fmt); commandProcessingResult = this.savingsApplicationProcessWritePlatformService.createActiveApplication(savingsAccountDataDTO); if (commandProcessingResult.getSavingsId() != null) { this.savingsRepositoryWrapper.findOneWithNotFoundDetection(commandProcessingResult.getSavingsId()); @@ -851,10 +851,10 @@ public CommandProcessingResult closeClient(final Long clientId, final JsonComman throw new InvalidClientStateTransitionException("close", "is.under.transfer", errorMessage); } - if (client.isNotPending() && client.getActivationLocalDate() != null && client.getActivationLocalDate().isAfter(closureDate)) { + if (client.isNotPending() && DateUtils.isAfter(client.getActivationDate(), closureDate)) { final String errorMessage = "The client closureDate cannot be before the client ActivationDate."; throw new InvalidClientStateTransitionException("close", "date.cannot.before.client.actvation.date", errorMessage, - closureDate, client.getActivationLocalDate()); + closureDate, client.getActivationDate()); } final LegalForm legalForm = LegalForm.fromInt(client.getLegalForm()); entityDatatableChecksWritePlatformService.runTheCheck(clientId, EntityTables.CLIENT.getName(), StatusEnum.CLOSE.getCode(), @@ -866,7 +866,7 @@ public CommandProcessingResult closeClient(final Long clientId, final JsonComman if (loanStatus.isOpen() || loanStatus.isPendingApproval() || loanStatus.isAwaitingDisbursal()) { final String errorMessage = "Client cannot be closed because of non-closed loans."; throw new InvalidClientStateTransitionException("close", "loan.non-closed", errorMessage); - } else if (loanStatus.isClosed() && loan.getClosedOnDate().isAfter(closureDate)) { + } else if (loanStatus.isClosed() && DateUtils.isAfter(loan.getClosedOnDate(), closureDate)) { final String errorMessage = "The client closureDate cannot be before the loan closedOnDate."; throw new InvalidClientStateTransitionException("close", "date.cannot.before.loan.closed.date", errorMessage, closureDate, loan.getClosedOnDate()); @@ -976,7 +976,7 @@ public CommandProcessingResult rejectClient(final Long entityId, final JsonComma final String errorMessage = "Only clients pending activation may be withdrawn."; throw new InvalidClientStateTransitionException("rejection", "on.account.not.in.pending.activation.status", errorMessage, rejectionDate, client.getSubmittedOnDate()); - } else if (client.getSubmittedOnDate().isAfter(rejectionDate)) { + } else if (DateUtils.isAfter(client.getSubmittedOnDate(), rejectionDate)) { final String errorMessage = "The client rejection date cannot be before the client submitted date."; throw new InvalidClientStateTransitionException("rejection", "date.cannot.before.client.submitted.date", errorMessage, rejectionDate, client.getSubmittedOnDate()); @@ -1008,7 +1008,7 @@ public CommandProcessingResult withdrawClient(Long entityId, JsonCommand command final String errorMessage = "Only clients pending activation may be withdrawn."; throw new InvalidClientStateTransitionException("withdrawal", "on.account.not.in.pending.activation.status", errorMessage, withdrawalDate, client.getSubmittedOnDate()); - } else if (client.getSubmittedOnDate().isAfter(withdrawalDate)) { + } else if (DateUtils.isAfter(client.getSubmittedOnDate(), withdrawalDate)) { final String errorMessage = "The client withdrawal date cannot be before the client submitted date."; throw new InvalidClientStateTransitionException("withdrawal", "date.cannot.before.client.submitted.date", errorMessage, withdrawalDate, client.getSubmittedOnDate()); @@ -1034,7 +1034,7 @@ public CommandProcessingResult reActivateClient(Long entityId, JsonCommand comma if (!client.isClosed()) { final String errorMessage = "only closed clients may be reactivated."; throw new InvalidClientStateTransitionException("reactivation", "on.nonclosed.account", errorMessage); - } else if (client.getClosureDate().isAfter(reactivateDate)) { + } else if (DateUtils.isAfter(client.getClosureDate(), reactivateDate)) { final String errorMessage = "The client reactivation date cannot be before the client closed date."; throw new InvalidClientStateTransitionException("reactivation", "date.cannot.before.client.closed.date", errorMessage, reactivateDate, client.getClosureDate()); @@ -1060,7 +1060,7 @@ public CommandProcessingResult undoRejection(Long entityId, JsonCommand command) if (!client.isRejected()) { final String errorMessage = "only rejected clients may be reactivated."; throw new InvalidClientStateTransitionException("undorejection", "on.nonrejected.account", errorMessage); - } else if (client.getRejectedDate().isAfter(undoRejectDate)) { + } else if (DateUtils.isAfter(client.getRejectedDate(), undoRejectDate)) { final String errorMessage = "The client reactivation date cannot be before the client rejected date."; throw new InvalidClientStateTransitionException("reopened", "date.cannot.before.client.rejected.date", errorMessage, undoRejectDate, client.getRejectedDate()); @@ -1088,7 +1088,7 @@ public CommandProcessingResult undoWithdrawal(Long entityId, JsonCommand command if (!client.isWithdrawn()) { final String errorMessage = "only withdrawal clients may be reactivated."; throw new InvalidClientStateTransitionException("undoWithdrawal", "on.nonwithdrawal.account", errorMessage); - } else if (client.getWithdrawalDate().isAfter(undoWithdrawalDate)) { + } else if (DateUtils.isAfter(client.getWithdrawalDate(), undoWithdrawalDate)) { final String errorMessage = "The client reactivation date cannot be before the client withdrawal date."; throw new InvalidClientStateTransitionException("reopened", "date.cannot.before.client.withdrawal.date", errorMessage, undoWithdrawalDate, client.getWithdrawalDate()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralsApiResourceSwagger.java index e06eb98b36a..4595fac7bcb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralsApiResourceSwagger.java @@ -38,7 +38,7 @@ static final class GetCollateralTypeResponse { private GetCollateralTypeResponse() {} @Schema(example = "8") - public Integer id; + public Long id; @Schema(example = "Gold") public String name; } @@ -62,7 +62,7 @@ private GetCollateralCurrencyResponse() {} } @Schema(example = "12") - public Integer id; + public Long id; public GetCollateralTypeResponse type; @Schema(example = "50000") public Long value; @@ -77,7 +77,7 @@ public static final class PostLoansLoanIdCollateralsRequest { private PostLoansLoanIdCollateralsRequest() {} @Schema(example = "9") - public Integer collateralTypeId; + public Long collateralTypeId; } @Schema(description = "PostLoansLoanIdCollateralsResponse") @@ -86,7 +86,7 @@ public static final class PostLoansLoanIdCollateralsResponse { private PostLoansLoanIdCollateralsResponse() {} @Schema(example = "12") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutLoansLoandIdCollateralsCollateralIdRequest") @@ -104,9 +104,9 @@ public static final class PutLoansLoanIdCollateralsCollateralIdResponse { private PutLoansLoanIdCollateralsCollateralIdResponse() {} @Schema(example = "1") - public Integer loanId; + public Long loanId; @Schema(example = "12") - public Integer resourceId; + public Long resourceId; public PutLoansLoandIdCollateralsCollateralIdRequest changes; } @@ -120,7 +120,7 @@ static final class GetCollateralsTemplateAllowedTypes { private GetCollateralsTemplateAllowedTypes() {} @Schema(example = "9") - public Integer id; + public Long id; @Schema(example = "Silver") public String name; @Schema(example = "0") @@ -136,8 +136,8 @@ public static final class DeleteLoansLoanIdCollateralsCollateralIdResponse { private DeleteLoansLoanIdCollateralsCollateralIdResponse() {} @Schema(example = "1") - public Integer loanId; + public Long loanId; @Schema(example = "12") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformServiceJpaRepositoryImpl.java index a258536992b..ea718011cf5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateral/service/CollateralWritePlatformServiceJpaRepositoryImpl.java @@ -138,7 +138,7 @@ public CommandProcessingResult updateCollateral(final Long loanId, final Long co .build(); } catch (final JpaSystemException | DataIntegrityViolationException dve) { handleCollateralDataIntegrityViolation(dve); - return new CommandProcessingResult(Long.valueOf(-1)); + return CommandProcessingResult.resourceResult(-1L); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/api/ClientCollateralManagementApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/api/ClientCollateralManagementApiResourceSwagger.java index 58501ff9201..f8037504451 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/api/ClientCollateralManagementApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/api/ClientCollateralManagementApiResourceSwagger.java @@ -112,9 +112,9 @@ public static final class PostClientCollateralResponse { private PostClientCollateralResponse() {} @Schema(example = "14") - public Integer resourceId; + public Long resourceId; @Schema(example = "1") - public Integer clientId; + public Long clientId; } @@ -136,9 +136,9 @@ public static final class PutClientCollateralResponse { private PutClientCollateralResponse() {} @Schema(example = "12") - public Integer resourceId; + public Long resourceId; @Schema(example = "1") - public Integer clientId; + public Long clientId; public PutClientCollateralRequest changes; } @@ -148,7 +148,7 @@ public static final class DeleteClientCollateralResponse { private DeleteClientCollateralResponse() {} @Schema(example = "12") - public Integer resourceId; + public Long resourceId; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/api/CollateralManagementApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/api/CollateralManagementApiResourceSwagger.java index 1f8ebee43a4..564e7b11b72 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/api/CollateralManagementApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/api/CollateralManagementApiResourceSwagger.java @@ -115,7 +115,7 @@ public static final class PostCollateralManagementProductResponse { private PostCollateralManagementProductResponse() {} @Schema(example = "14") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutCollateralProductRequest") @@ -144,7 +144,7 @@ public static final class PutCollateralProductResponse { private PutCollateralProductResponse() {} @Schema(example = "12") - public Integer resourceId; + public Long resourceId; public CollateralManagementApiResourceSwagger.PutCollateralProductRequest changes; } @@ -154,6 +154,6 @@ public static final class DeleteCollateralProductResponse { private DeleteCollateralProductResponse() {} @Schema(example = "12") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/ClientCollateralManagementReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/ClientCollateralManagementReadPlatformServiceImpl.java index 0286cc19a97..27d3fbfff05 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/ClientCollateralManagementReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/ClientCollateralManagementReadPlatformServiceImpl.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.collateralmanagement.data.ClientCollateralManagementData; import org.apache.fineract.portfolio.collateralmanagement.data.LoanCollateralTemplateData; @@ -33,25 +34,14 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ClientCollateralManagementReadPlatformServiceImpl implements ClientCollateralManagementReadPlatformService { private final PlatformSecurityContext context; private final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper; private final LoanTransactionRepository loanTransactionRepository; - @Autowired - public ClientCollateralManagementReadPlatformServiceImpl(final PlatformSecurityContext context, - final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper, - final LoanTransactionRepository loanTransactionRepository) { - this.context = context; - this.clientCollateralManagementRepositoryWrapper = clientCollateralManagementRepositoryWrapper; - this.loanTransactionRepository = loanTransactionRepository; - } - @Override public List getClientCollaterals(final Long clientId, final Long prodId) { return this.clientCollateralManagementRepositoryWrapper.getClientCollateralData(clientId, prodId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/ClientCollateralManagementWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/ClientCollateralManagementWritePlatformServiceImpl.java index 95576b97b80..67d8ddb771a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/ClientCollateralManagementWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/ClientCollateralManagementWritePlatformServiceImpl.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -39,26 +40,14 @@ import org.apache.fineract.portfolio.collateralmanagement.exception.ClientCollateralNotFoundException; import org.apache.fineract.portfolio.collateralmanagement.exception.CollateralNotFoundException; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ClientCollateralManagementWritePlatformServiceImpl implements ClientCollateralManagementWritePlatformService { private final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper; private final CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper; private final ClientRepositoryWrapper clientRepositoryWrapper; - @Autowired - public ClientCollateralManagementWritePlatformServiceImpl( - final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper, - final CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper, - final ClientRepositoryWrapper clientRepositoryWrapper) { - this.clientCollateralManagementRepositoryWrapper = clientCollateralManagementRepositoryWrapper; - this.collateralManagementRepositoryWrapper = collateralManagementRepositoryWrapper; - this.clientRepositoryWrapper = clientRepositoryWrapper; - } - @Transactional @Override public CommandProcessingResult addClientCollateralProduct(final JsonCommand command) { @@ -136,7 +125,7 @@ private void validateForUpdate(final JsonCommand command) { } BigDecimal totalQuantity = BigDecimal.ZERO; - if (clientCollateralManagement.getLoanCollateralManagementSet().size() > 0) { + if (!clientCollateralManagement.getLoanCollateralManagementSet().isEmpty()) { for (LoanCollateralManagement loanCollateralManagement : clientCollateralManagement.getLoanCollateralManagementSet()) { totalQuantity = totalQuantity.add(loanCollateralManagement.getQuantity()); } @@ -167,7 +156,7 @@ private void validateForDeletion(final ClientCollateralManagement clientCollater throw new CollateralNotFoundException(clientCollateralId); } - if (clientCollateralManagement.getLoanCollateralManagementSet().size() > 0) { + if (!clientCollateralManagement.getLoanCollateralManagementSet().isEmpty()) { for (LoanCollateralManagement loanCollateralManagement : clientCollateralManagement.getLoanCollateralManagementSet()) { if (!loanCollateralManagement.isReleased()) { throw new ClientCollateralCannotBeDeletedException( diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/CollateralManagementReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/CollateralManagementReadPlatformServiceImpl.java index 2d61ceca94e..47432cee6bb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/CollateralManagementReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/CollateralManagementReadPlatformServiceImpl.java @@ -20,26 +20,18 @@ import java.util.ArrayList; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.collateralmanagement.data.CollateralManagementData; import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomain; import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementRepositoryWrapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class CollateralManagementReadPlatformServiceImpl implements CollateralManagementReadPlatformService { private final PlatformSecurityContext context; private final CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper; - @Autowired - public CollateralManagementReadPlatformServiceImpl(final PlatformSecurityContext context, - final CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper) { - this.context = context; - this.collateralManagementRepositoryWrapper = collateralManagementRepositoryWrapper; - } - @Override public CollateralManagementData getCollateralProduct(Long collateralId) { final CollateralManagementDomain collateralManagementDomain = this.collateralManagementRepositoryWrapper diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/CollateralManagementWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/CollateralManagementWritePlatformServiceImpl.java index fcaca36d254..e6a122185ed 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/CollateralManagementWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/CollateralManagementWritePlatformServiceImpl.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -38,25 +39,15 @@ import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementRepositoryWrapper; import org.apache.fineract.portfolio.collateralmanagement.exception.CollateralCannotBeDeletedException; import org.apache.fineract.portfolio.collateralmanagement.exception.CollateralNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class CollateralManagementWritePlatformServiceImpl implements CollateralManagementWritePlatformService { private final CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper; private final ApplicationCurrencyRepository applicationCurrencyRepository; private final FromJsonHelper fromApiJsonHelper; - @Autowired - public CollateralManagementWritePlatformServiceImpl(final CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper, - final ApplicationCurrencyRepository applicationCurrencyRepository, final FromJsonHelper fromApiJsonHelper) { - this.collateralManagementRepositoryWrapper = collateralManagementRepositoryWrapper; - this.applicationCurrencyRepository = applicationCurrencyRepository; - this.fromApiJsonHelper = fromApiJsonHelper; - } - @Transactional @Override public CommandProcessingResult createCollateral(final JsonCommand jsonCommand) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralAssembler.java index 37b6f48a1bd..26bdeeb4872 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralAssembler.java @@ -25,6 +25,7 @@ import java.util.HashSet; import java.util.Locale; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement; @@ -33,10 +34,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagementRepository; import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollateralQuantity; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class LoanCollateralAssembler { private final FromJsonHelper fromApiJsonHelper; @@ -44,16 +43,6 @@ public class LoanCollateralAssembler { private final LoanCollateralManagementRepository loanCollateralRepository; private final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper; - @Autowired - public LoanCollateralAssembler(final FromJsonHelper fromApiJsonHelper, final CodeValueRepositoryWrapper codeValueRepository, - final LoanCollateralManagementRepository loanCollateralRepository, - final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper) { - this.fromApiJsonHelper = fromApiJsonHelper; - this.codeValueRepository = codeValueRepository; - this.loanCollateralRepository = loanCollateralRepository; - this.clientCollateralManagementRepositoryWrapper = clientCollateralManagementRepositoryWrapper; - } - public Set fromParsedJson(final JsonElement element) { final Set collateralItems = new HashSet<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralManagementReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralManagementReadPlatformServiceImpl.java index 88d2a7f19ac..74dc7787dae 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralManagementReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralManagementReadPlatformServiceImpl.java @@ -31,11 +31,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagementRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service @Transactional(readOnly = true) public class LoanCollateralManagementReadPlatformServiceImpl implements LoanCollateralManagementReadPlatformService { @@ -43,7 +40,6 @@ public class LoanCollateralManagementReadPlatformServiceImpl implements LoanColl private LoanCollateralManagementRepository loanCollateralManagementRepository; private LoanRepository loanRepository; - @Autowired public LoanCollateralManagementReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanCollateralManagementRepository loanCollateralManagementRepository, final LoanRepository loanRepository) { this.context = context; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralManagementWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralManagementWritePlatformServiceImpl.java index 3f53fc76a27..248e9e231ba 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralManagementWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/service/LoanCollateralManagementWritePlatformServiceImpl.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.collateralmanagement.service; import java.math.BigDecimal; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; @@ -26,23 +27,14 @@ import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagementRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class LoanCollateralManagementWritePlatformServiceImpl implements LoanCollateralManagementWritePlatformService { private final LoanCollateralManagementRepository loanCollateralManagementRepository; private final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper; - @Autowired - public LoanCollateralManagementWritePlatformServiceImpl(final LoanCollateralManagementRepository loanCollateralManagementRepository, - final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper) { - this.loanCollateralManagementRepository = loanCollateralManagementRepository; - this.clientCollateralManagementRepositoryWrapper = clientCollateralManagementRepositoryWrapper; - } - @Transactional @Override public CommandProcessingResult deleteLoanCollateral(JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/starter/CollateralManagementConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/starter/CollateralManagementConfiguration.java new file mode 100644 index 00000000000..4c30c398769 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collateralmanagement/starter/CollateralManagementConfiguration.java @@ -0,0 +1,109 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.collateralmanagement.starter; + +import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepository; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepositoryWrapper; +import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementRepositoryWrapper; +import org.apache.fineract.portfolio.collateralmanagement.service.ClientCollateralManagementReadPlatformService; +import org.apache.fineract.portfolio.collateralmanagement.service.ClientCollateralManagementReadPlatformServiceImpl; +import org.apache.fineract.portfolio.collateralmanagement.service.ClientCollateralManagementWritePlatformService; +import org.apache.fineract.portfolio.collateralmanagement.service.ClientCollateralManagementWritePlatformServiceImpl; +import org.apache.fineract.portfolio.collateralmanagement.service.CollateralManagementReadPlatformService; +import org.apache.fineract.portfolio.collateralmanagement.service.CollateralManagementReadPlatformServiceImpl; +import org.apache.fineract.portfolio.collateralmanagement.service.CollateralManagementWritePlatformService; +import org.apache.fineract.portfolio.collateralmanagement.service.CollateralManagementWritePlatformServiceImpl; +import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler; +import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralManagementReadPlatformService; +import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralManagementReadPlatformServiceImpl; +import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralManagementWritePlatformService; +import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralManagementWritePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagementRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CollateralManagementConfiguration { + + @Bean + @ConditionalOnMissingBean(ClientCollateralManagementReadPlatformService.class) + public ClientCollateralManagementReadPlatformService clientCollateralManagementReadPlatformService(PlatformSecurityContext context, + ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper, + LoanTransactionRepository loanTransactionRepository) { + return new ClientCollateralManagementReadPlatformServiceImpl(context, clientCollateralManagementRepositoryWrapper, + loanTransactionRepository); + } + + @Bean + @ConditionalOnMissingBean(ClientCollateralManagementWritePlatformService.class) + public ClientCollateralManagementWritePlatformService clientCollateralManagementWritePlatformService( + ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper, + CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper, ClientRepositoryWrapper clientRepositoryWrapper) { + return new ClientCollateralManagementWritePlatformServiceImpl(clientCollateralManagementRepositoryWrapper, + collateralManagementRepositoryWrapper, clientRepositoryWrapper); + } + + @Bean + @ConditionalOnMissingBean(CollateralManagementReadPlatformService.class) + public CollateralManagementReadPlatformService collateralManagementReadPlatformService(PlatformSecurityContext context, + CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper) { + return new CollateralManagementReadPlatformServiceImpl(context, collateralManagementRepositoryWrapper); + } + + @Bean + @ConditionalOnMissingBean(CollateralManagementWritePlatformService.class) + public CollateralManagementWritePlatformService collateralManagementWritePlatformService( + CollateralManagementRepositoryWrapper collateralManagementRepositoryWrapper, + ApplicationCurrencyRepository applicationCurrencyRepository, FromJsonHelper fromApiJsonHelper) { + return new CollateralManagementWritePlatformServiceImpl(collateralManagementRepositoryWrapper, applicationCurrencyRepository, + fromApiJsonHelper); + } + + @Bean + @ConditionalOnMissingBean(LoanCollateralAssembler.class) + public LoanCollateralAssembler loanCollateralAssembler(FromJsonHelper fromApiJsonHelper, CodeValueRepositoryWrapper codeValueRepository, + LoanCollateralManagementRepository loanCollateralRepository, + ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper) { + return new LoanCollateralAssembler(fromApiJsonHelper, codeValueRepository, loanCollateralRepository, + clientCollateralManagementRepositoryWrapper); + } + + @Bean + @ConditionalOnMissingBean(LoanCollateralManagementReadPlatformService.class) + public LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService(PlatformSecurityContext context, + LoanCollateralManagementRepository loanCollateralManagementRepository, LoanRepository loanRepository) { + return new LoanCollateralManagementReadPlatformServiceImpl(context, loanCollateralManagementRepository, loanRepository); + } + + @Bean + @ConditionalOnMissingBean(LoanCollateralManagementWritePlatformService.class) + public LoanCollateralManagementWritePlatformService loanCollateralManagementWritePlatformService( + LoanCollateralManagementRepository loanCollateralManagementRepository, + ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper) { + return new LoanCollateralManagementWritePlatformServiceImpl(loanCollateralManagementRepository, + clientCollateralManagementRepositoryWrapper); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/api/CollectionSheetApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/api/CollectionSheetApiResourceSwagger.java index b3112730d15..74b6f70bbf6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/api/CollectionSheetApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/api/CollectionSheetApiResourceSwagger.java @@ -39,11 +39,11 @@ static final class PostCollectionSheetBulkRepaymentTransactions { private PostCollectionSheetBulkRepaymentTransactions() {} @Schema(example = "10") - public Integer loanId; + public Long loanId; @Schema(example = "1221.36") public Double transactionAmount; @Schema(example = "19") - public Integer paymentTypeId; + public Long paymentTypeId; @Schema(example = "1245356") public Long receiptNumber; } @@ -81,9 +81,9 @@ private PostCollectionSheetChanges() {} } @Schema(example = "10") - public Integer groupId; + public Long groupId; @Schema(example = "10") - public Integer resourceId; + public Long resourceId; public PostCollectionSheetChanges changes; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java index 4d9568b2877..97d7a14892b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java @@ -68,16 +68,13 @@ import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService; import org.apache.fineract.portfolio.savings.data.SavingsProductData; import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.stereotype.Service; -@Service public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetReadPlatformService { private final PlatformSecurityContext context; @@ -95,7 +92,6 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe private final CalendarInstanceRepository calendarInstanceRepository; private final DatabaseSpecificSQLGenerator sqlGenerator; - @Autowired public CollectionSheetReadPlatformServiceImpl(final PlatformSecurityContext context, final NamedParameterJdbcTemplate namedParameterJdbcTemplate, final CenterReadPlatformService centerReadPlatformService, final GroupReadPlatformService groupReadPlatformService, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformServiceJpaRepositoryImpl.java index d1cfc1ac967..61132d72f02 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetWritePlatformServiceJpaRepositoryImpl.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -41,10 +42,8 @@ import org.apache.fineract.portfolio.savings.domain.DepositAccountAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class CollectionSheetWritePlatformServiceJpaRepositoryImpl implements CollectionSheetWritePlatformService { private final LoanWritePlatformService loanWritePlatformService; @@ -57,25 +56,6 @@ public class CollectionSheetWritePlatformServiceJpaRepositoryImpl implements Col private final PaymentDetailAssembler paymentDetailAssembler; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; - @Autowired - public CollectionSheetWritePlatformServiceJpaRepositoryImpl(final LoanWritePlatformService loanWritePlatformService, - final CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer bulkRepaymentCommandFromApiJsonDeserializer, - final CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer bulkDisbursalCommandFromApiJsonDeserializer, - final CollectionSheetTransactionDataValidator transactionDataValidator, - final MeetingWritePlatformService meetingWritePlatformService, final DepositAccountAssembler accountAssembler, - final DepositAccountWritePlatformService accountWritePlatformService, final PaymentDetailAssembler paymentDetailAssembler, - final PaymentDetailWritePlatformService paymentDetailWritePlatformService) { - this.loanWritePlatformService = loanWritePlatformService; - this.bulkRepaymentCommandFromApiJsonDeserializer = bulkRepaymentCommandFromApiJsonDeserializer; - this.bulkDisbursalCommandFromApiJsonDeserializer = bulkDisbursalCommandFromApiJsonDeserializer; - this.transactionDataValidator = transactionDataValidator; - this.meetingWritePlatformService = meetingWritePlatformService; - this.accountAssembler = accountAssembler; - this.accountWritePlatformService = accountWritePlatformService; - this.paymentDetailAssembler = paymentDetailAssembler; - this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; - } - @Override public CommandProcessingResult updateCollectionSheet(final JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/starter/CollectionSheetConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/starter/CollectionSheetConfiguration.java new file mode 100644 index 00000000000..d684f330cca --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/starter/CollectionSheetConfiguration.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.collectionsheet.starter; + +import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; +import org.apache.fineract.portfolio.calendar.domain.CalendarRepositoryWrapper; +import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; +import org.apache.fineract.portfolio.collectionsheet.data.CollectionSheetTransactionDataValidator; +import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer; +import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer; +import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetGenerateCommandFromApiJsonDeserializer; +import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetReadPlatformService; +import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetReadPlatformServiceImpl; +import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetWritePlatformService; +import org.apache.fineract.portfolio.collectionsheet.service.CollectionSheetWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.group.service.CenterReadPlatformService; +import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService; +import org.apache.fineract.portfolio.meeting.attendance.service.AttendanceDropdownReadPlatformService; +import org.apache.fineract.portfolio.meeting.service.MeetingWritePlatformService; +import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailAssembler; +import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; +import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService; +import org.apache.fineract.portfolio.savings.domain.DepositAccountAssembler; +import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +@Configuration +public class CollectionSheetConfiguration { + + @Bean + @ConditionalOnMissingBean(CollectionSheetReadPlatformService.class) + public CollectionSheetReadPlatformService collectionSheetReadPlatformService(PlatformSecurityContext context, + NamedParameterJdbcTemplate namedParameterJdbcTemplate, CenterReadPlatformService centerReadPlatformService, + GroupReadPlatformService groupReadPlatformService, + CollectionSheetGenerateCommandFromApiJsonDeserializer collectionSheetGenerateCommandFromApiJsonDeserializer, + CalendarRepositoryWrapper calendarRepositoryWrapper, + AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService, + CodeValueReadPlatformService codeValueReadPlatformService, PaymentTypeReadPlatformService paymentTypeReadPlatformService, + CalendarReadPlatformService calendarReadPlatformService, ConfigurationDomainService configurationDomainService, + CalendarInstanceRepository calendarInstanceRepository, DatabaseSpecificSQLGenerator sqlGenerator) { + return new CollectionSheetReadPlatformServiceImpl(context, namedParameterJdbcTemplate, centerReadPlatformService, + groupReadPlatformService, collectionSheetGenerateCommandFromApiJsonDeserializer, calendarRepositoryWrapper, + attendanceDropdownReadPlatformService, codeValueReadPlatformService, paymentTypeReadPlatformService, + calendarReadPlatformService, configurationDomainService, calendarInstanceRepository, sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(CollectionSheetWritePlatformService.class) + public CollectionSheetWritePlatformService collectionSheetWritePlatformService(LoanWritePlatformService loanWritePlatformService, + CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer bulkRepaymentCommandFromApiJsonDeserializer, + CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer bulkDisbursalCommandFromApiJsonDeserializer, + CollectionSheetTransactionDataValidator transactionDataValidator, MeetingWritePlatformService meetingWritePlatformService, + DepositAccountAssembler accountAssembler, DepositAccountWritePlatformService accountWritePlatformService, + PaymentDetailAssembler paymentDetailAssembler, PaymentDetailWritePlatformService paymentDetailWritePlatformService) { + return new CollectionSheetWritePlatformServiceJpaRepositoryImpl(loanWritePlatformService, + bulkRepaymentCommandFromApiJsonDeserializer, bulkDisbursalCommandFromApiJsonDeserializer, transactionDataValidator, + meetingWritePlatformService, accountAssembler, accountWritePlatformService, paymentDetailAssembler, + paymentDetailWritePlatformService); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiResourceSwagger.java index 36dcc9c440f..65b8ccdcb7f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiResourceSwagger.java @@ -31,13 +31,13 @@ public static final class GetDelinquencyRangesResponse { private GetDelinquencyRangesResponse() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Delinquent 1") public String classification; @Schema(example = "1") - public Long minimumAgeDays; + public Integer minimumAgeDays; @Schema(example = "3") - public Long maximumAgeDays; + public Integer maximumAgeDays; } @Schema(description = "GetDelinquencyBucketsResponse") @@ -46,7 +46,7 @@ public static final class GetDelinquencyBucketsResponse { private GetDelinquencyBucketsResponse() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Delinquent Bucket Set 1") public String name; @@ -140,7 +140,7 @@ public static final class GetDelinquencyTagHistoryResponse { private GetDelinquencyTagHistoryResponse() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "10") public Long loanId; public GetDelinquencyRangesResponse delinquencyRange; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/TenantDateTimeUtil.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java similarity index 50% rename from fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/TenantDateTimeUtil.java rename to fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java index 506709cdf75..860156951e7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/TenantDateTimeUtil.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java @@ -16,26 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.infrastructure.campaigns.jobs; +package org.apache.fineract.portfolio.delinquency.data; -import java.time.LocalDateTime; -import java.time.ZoneId; -import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import java.math.BigDecimal; -public final class TenantDateTimeUtil { +public interface LoanInstallmentDelinquencyTagData { - private TenantDateTimeUtil() {} + InstallmentDelinquencyRange getDelinquencyRange(); - public static LocalDateTime tenantDateTime() { - LocalDateTime today = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); - final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); + BigDecimal getOutstandingAmount(); - if (tenant != null) { - final ZoneId zone = ZoneId.of(tenant.getTimezoneId()); - today = LocalDateTime.now(zone); - } - return today; + interface InstallmentDelinquencyRange { + + Long getId(); + + String getClassification(); + + Integer getMinimumAgeDays(); + + Integer getMaximumAgeDays(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyAction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyAction.java new file mode 100644 index 00000000000..978e6edfe88 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyAction.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.delinquency.domain; + +public enum DelinquencyAction { + PAUSE, RESUME +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyAction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyAction.java new file mode 100644 index 00000000000..fed9b5c8ed4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyAction.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.delinquency.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.time.LocalDate; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; + +@Getter +@Setter +@NoArgsConstructor +@Entity +@Table(name = "m_loan_delinquency_action") +public class LoanDelinquencyAction extends AbstractAuditableWithUTCDateTimeCustom { + + @ManyToOne + @JoinColumn(name = "loan_id", nullable = false) + private Loan loan; + + @Enumerated(EnumType.STRING) + @Column(name = "action", nullable = false) + private DelinquencyAction action; + + @Column(name = "start_date", nullable = false) + private LocalDate startDate; + + @Column(name = "end_date", nullable = true) + private LocalDate endDate; + + public LoanDelinquencyAction(Loan loan, DelinquencyAction action, LocalDate startDate, LocalDate endDate) { + this.loan = loan; + this.action = action; + this.startDate = startDate; + this.endDate = endDate; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyActionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyActionRepository.java new file mode 100644 index 00000000000..d8bef639eca --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyActionRepository.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.delinquency.domain; + +import java.time.LocalDate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface LoanDelinquencyActionRepository + extends JpaRepository, JpaSpecificationExecutor { + + @Query(value = "select da from LoanDelinquencyAction da where " + + " ((da.action = org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.PAUSE and da.endDate is not null and :business_date >= da.startDate and :business_date <= da.endDate) or" + + " (da.action = org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME and da.endDate is null and :business_date >= da.startDate)) and" + + " da.loan.id = :loan_id order by da.createdDate desc") + Page getEffectiveDelinquencyActionForLoan(@Param("loan_id") Long loan_id, + @Param("business_date") LocalDate business_date, Pageable page); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTag.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTag.java new file mode 100644 index 00000000000..248c49d5128 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTag.java @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.delinquency.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Version; +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; + +@Getter +@Setter +@NoArgsConstructor +@Entity +@Table(name = "m_loan_installment_delinquency_tag") +public class LoanInstallmentDelinquencyTag extends AbstractAuditableWithUTCDateTimeCustom { + + @ManyToOne + @JoinColumn(name = "delinquency_range_id", nullable = false) + private DelinquencyRange delinquencyRange; + + @ManyToOne + @JoinColumn(name = "loan_id", nullable = false) + private Loan loan; + + @ManyToOne + @JoinColumn(name = "installment_id", nullable = false) + private LoanRepaymentScheduleInstallment installment; + + @Column(name = "addedon_date", nullable = false) + private LocalDate addedOnDate; + + @Column(name = "liftedon_date", nullable = true) + private LocalDate liftedOnDate; + + @Column(name = "first_overdue_date", nullable = false) + private LocalDate firstOverdueDate; + + @Column(name = "outstanding_amount", scale = 6, precision = 19) + private BigDecimal outstandingAmount; + + @Version + private Long version; + + public LoanInstallmentDelinquencyTag(DelinquencyRange delinquencyRange, Loan loan, LoanRepaymentScheduleInstallment installment, + LocalDate addedOnDate, LocalDate liftedOnDate, LocalDate firstOverdueDate, BigDecimal outstandingAmount) { + this.delinquencyRange = delinquencyRange; + this.loan = loan; + this.installment = installment; + this.addedOnDate = addedOnDate; + this.liftedOnDate = liftedOnDate; + this.firstOverdueDate = firstOverdueDate; + this.outstandingAmount = outstandingAmount; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java new file mode 100644 index 00000000000..5729fdec66e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.delinquency.domain; + +import java.util.List; +import java.util.Optional; +import org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface LoanInstallmentDelinquencyTagRepository + extends JpaRepository, JpaSpecificationExecutor { + + Optional findByLoanAndInstallment(Loan loan, LoanRepaymentScheduleInstallment installment); + + @Query("select i from LoanInstallmentDelinquencyTag i where i.loan.id = :loanId") + List findByLoanId(@Param("loanId") Long loanId); + + // Fetching Installment Delinquency range and outstanding amount + @Query("select i.delinquencyRange, i.outstandingAmount from LoanInstallmentDelinquencyTag i where i.loan.id = :loanId") + List findInstallmentDelinquencyTags(@Param("loanId") Long loanId); + + @Modifying(flushAutomatically = true) + @Query("delete from LoanInstallmentDelinquencyTag i where i.loan.id = :loanId") + void deleteAllLoanInstallmentsTags(@Param("loanId") Long loanId); + + @Modifying(flushAutomatically = true) + @Query("delete from LoanInstallmentDelinquencyTag i where i.id IN :tagIds") + void deleteAllLoanInstallmentsTagsByIds(@Param("tagIds") List tagIds); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java index b1c2dad11c1..9f3510cce57 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java @@ -22,6 +22,7 @@ import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData; import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData; +import org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData; import org.apache.fineract.portfolio.loanaccount.data.CollectionData; public interface DelinquencyReadPlatformService { @@ -40,4 +41,6 @@ public interface DelinquencyReadPlatformService { CollectionData calculateLoanCollectionData(Long loanId); + Collection retrieveLoanInstallmentsCurrentDelinquencyTag(Long loanId); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java index c98b493277a..7e1d9c1455e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java @@ -25,12 +25,14 @@ import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData; import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData; +import org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; +import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository; import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper; import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper; import org.apache.fineract.portfolio.delinquency.mapper.LoanDelinquencyTagMapper; @@ -52,6 +54,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo private final LoanDelinquencyTagMapper mapperLoanDelinquencyTagHistory; private final LoanRepository loanRepository; private final LoanDelinquencyDomainService loanDelinquencyDomainService; + private final LoanInstallmentDelinquencyTagRepository repositoryLoanInstallmentDelinquencyTag; @Override public Collection retrieveAllDelinquencyRanges() { @@ -126,4 +129,9 @@ public CollectionData calculateLoanCollectionData(final Long loanId) { return collectionData; } + @Override + public Collection retrieveLoanInstallmentsCurrentDelinquencyTag(Long loanId) { + return repositoryLoanInstallmentDelinquencyTag.findInstallmentDelinquencyTags(loanId); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java index 883a4780867..0520c36d156 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java @@ -46,13 +46,17 @@ import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; +import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTag; +import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository; import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketAgesOverlapedException; import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeInvalidAgesException; import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator; import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator; import org.apache.fineract.portfolio.loanaccount.data.CollectionData; +import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; @@ -71,6 +75,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat private final LoanProductRepository loanProductRepository; private final BusinessEventNotifierService businessEventNotifierService; private final LoanDelinquencyDomainService loanDelinquencyDomainService; + private final LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository; @Override public CommandProcessingResult createDelinquencyRange(JsonCommand command) { @@ -158,8 +163,17 @@ public CommandProcessingResult applyDelinquencyTagToLoan(Long loanId, JsonComman final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId); final DelinquencyBucket delinquencyBucket = loan.getLoanProduct().getDelinquencyBucket(); if (delinquencyBucket != null) { - final CollectionData collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan); + final LoanDelinquencyData loanDelinquencyData = loanDelinquencyDomainService.getLoanDelinquencyData(loan); + // loan delinquent data + final CollectionData collectionData = loanDelinquencyData.getLoanCollectionData(); + // loan installments delinquent data + final Map installmentsCollectionData = loanDelinquencyData.getLoanInstallmentsCollectionData(); + // delinquency for loan changes = lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays()); + // delinquency for installments + if (installmentsCollectionData.size() > 0) { + applyDelinquencyDetailsForLoanInstallments(loan, delinquencyBucket, installmentsCollectionData); + } } return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId()) .withEntityExternalId(loan.getExternalId()).with(changes).build(); @@ -170,15 +184,27 @@ public void applyDelinquencyTagToLoan(LoanScheduleDelinquencyData loanDelinquenc final Loan loan = loanDelinquencyData.getLoan(); if (loan.hasDelinquencyBucket()) { final DelinquencyBucket delinquencyBucket = loan.getLoanProduct().getDelinquencyBucket(); - final CollectionData collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan); + final LoanDelinquencyData loanDelinquentData = loanDelinquencyDomainService.getLoanDelinquencyData(loan); + // loan delinquent data + final CollectionData collectionData = loanDelinquentData.getLoanCollectionData(); + // loan installments delinquent data + final Map installmentsCollectionData = loanDelinquentData.getLoanInstallmentsCollectionData(); log.debug("Delinquency {}", collectionData); + // delinquency for loan lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays()); + // delinquency for installments + if (installmentsCollectionData.size() > 0) { + applyDelinquencyDetailsForLoanInstallments(loan, delinquencyBucket, installmentsCollectionData); + } } } @Override public void removeDelinquencyTagToLoan(final Loan loan) { setLoanDelinquencyTag(loan, null); + if (loan.isEnableInstallmentLevelDelinquency()) { + cleanLoanInstallmentsDelinquencyTags(loan); + } } @Override @@ -382,4 +408,107 @@ public int compare(DelinquencyRange o1, DelinquencyRange o2) { return ranges; } + private void applyDelinquencyDetailsForLoanInstallments(final Loan loan, final DelinquencyBucket delinquencyBucket, + final Map installmentsCollectionData) { + for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { + if (installmentsCollectionData.containsKey(installment.getId())) { + setInstallmentDelinquencyDetails(loan, installment, delinquencyBucket, installmentsCollectionData.get(installment.getId())); + } + } + // remove tags for non existing installments that got deleted due to re-schedule + removeDelinquencyTagsForNonExistingInstallments(loan.getId()); + } + + private void setInstallmentDelinquencyDetails(final Loan loan, final LoanRepaymentScheduleInstallment installment, + final DelinquencyBucket delinquencyBucket, final CollectionData installmentDelinquencyData) { + DelinquencyRange delinquencyRangeForInstallment = getInstallmentDelinquencyRange(delinquencyBucket, + installmentDelinquencyData.getDelinquentDays()); + setDelinquencyDetailsForInstallment(loan, installment, installmentDelinquencyData, delinquencyRangeForInstallment); + } + + private DelinquencyRange getInstallmentDelinquencyRange(final DelinquencyBucket delinquencyBucket, Long overDueDays) { + DelinquencyRange delinquencyRangeForInstallment = null; + if (overDueDays > 0) { + // Sort the ranges based on the minAgeDays + final List ranges = sortDelinquencyRangesByMinAge(delinquencyBucket.getRanges()); + for (final DelinquencyRange delinquencyRange : ranges) { + if (delinquencyRange.getMaximumAgeDays() == null) { // Last Range in the Bucket + if (delinquencyRange.getMinimumAgeDays() <= overDueDays) { + delinquencyRangeForInstallment = delinquencyRange; + break; + } + } else { + if (delinquencyRange.getMinimumAgeDays() <= overDueDays && delinquencyRange.getMaximumAgeDays() >= overDueDays) { + delinquencyRangeForInstallment = delinquencyRange; + break; + } + } + } + + } + return delinquencyRangeForInstallment; + } + + private void setDelinquencyDetailsForInstallment(final Loan loan, final LoanRepaymentScheduleInstallment installment, + CollectionData installmentDelinquencyData, final DelinquencyRange delinquencyRangeForInstallment) { + List installmentDelinquencyTags = new ArrayList<>(); + LocalDate delinquencyCalculationDate = DateUtils.getBusinessLocalDate(); + + LoanInstallmentDelinquencyTag previousInstallmentDelinquencyTag = loanInstallmentDelinquencyTagRepository + .findByLoanAndInstallment(loan, installment).orElse(null); + + if (delinquencyRangeForInstallment == null) { + // if currentInstallmentDelinquencyTag exists and range is null, installment is out of delinquency, delete + // delinquency details + if (previousInstallmentDelinquencyTag != null) { + // event installment out of delinquency + loanInstallmentDelinquencyTagRepository.delete(previousInstallmentDelinquencyTag); + } + } else { + LoanInstallmentDelinquencyTag installmentDelinquency = null; + if (previousInstallmentDelinquencyTag != null) { + if (!previousInstallmentDelinquencyTag.getDelinquencyRange().getId().equals(delinquencyRangeForInstallment.getId())) { + // if current delinquency range exists and there is range change, delete previous delinquency + // details and add new range details + installmentDelinquency = new LoanInstallmentDelinquencyTag(delinquencyRangeForInstallment, loan, installment, + delinquencyCalculationDate, null, previousInstallmentDelinquencyTag.getFirstOverdueDate(), + installmentDelinquencyData.getDelinquentAmount()); + loanInstallmentDelinquencyTagRepository.delete(previousInstallmentDelinquencyTag); + // event installment delinquency range change + } + } else { + // add new range, first time delinquent + installmentDelinquency = new LoanInstallmentDelinquencyTag(delinquencyRangeForInstallment, loan, installment, + delinquencyCalculationDate, null, installmentDelinquencyData.getDelinquentDate(), + installmentDelinquencyData.getDelinquentAmount()); + // event installment delinquent + } + + if (installmentDelinquency != null) { + installmentDelinquencyTags.add(installmentDelinquency); + } + + } + + if (installmentDelinquencyTags.size() > 0) { + loanInstallmentDelinquencyTagRepository.saveAllAndFlush(installmentDelinquencyTags); + } + + } + + private void cleanLoanInstallmentsDelinquencyTags(Loan loan) { + loanInstallmentDelinquencyTagRepository.deleteAllLoanInstallmentsTags(loan.getId()); + } + + private void removeDelinquencyTagsForNonExistingInstallments(Long loanId) { + List currentLoanInstallmentDelinquencyTags = loanInstallmentDelinquencyTagRepository + .findByLoanId(loanId); + if (currentLoanInstallmentDelinquencyTags != null && currentLoanInstallmentDelinquencyTags.size() > 0) { + List loanInstallmentTagsForDelete = currentLoanInstallmentDelinquencyTags.stream() + .filter(tag -> tag.getInstallment() == null).map(tag -> tag.getId()).toList(); + if (loanInstallmentTagsForDelete.size() > 0) { + loanInstallmentDelinquencyTagRepository.deleteAllLoanInstallmentsTagsByIds(loanInstallmentTagsForDelete); + } + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java index 5cd19cb5f3b..656ceb6b7ae 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainService.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.delinquency.service; import org.apache.fineract.portfolio.loanaccount.data.CollectionData; +import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; public interface LoanDelinquencyDomainService { @@ -31,4 +32,6 @@ public interface LoanDelinquencyDomainService { */ CollectionData getOverdueCollectionData(Loan loan); + LoanDelinquencyData getLoanDelinquencyData(Loan loan); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java index 099e20b8163..67085cd6503 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java @@ -20,12 +20,15 @@ import java.math.BigDecimal; import java.time.LocalDate; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.portfolio.loanaccount.data.CollectionData; +import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @@ -42,79 +45,104 @@ public CollectionData getOverdueCollectionData(final Loan loan) { final MonetaryCurrency loanCurrency = loan.getCurrency(); LocalDate overdueSinceDate = null; CollectionData collectionData = CollectionData.template(); - BigDecimal amountAvailable; BigDecimal outstandingAmount = BigDecimal.ZERO; boolean oldestOverdueInstallment = false; boolean overdueSinceDateWasSet = false; boolean firstNotYetDueInstallment = false; - LoanRepaymentScheduleInstallment latestInstallment = loan.getLastLoanRepaymentScheduleInstallment(); - - List chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback); - log.debug("Loan id {} with {} installments", loan.getId(), loan.getRepaymentScheduleInstallments().size()); // Get the oldest overdue installment if exists one for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { if (!installment.isObligationsMet()) { - if (installment.getDueDate().isBefore(businessDate)) { + if (DateUtils.isBefore(installment.getDueDate(), businessDate)) { log.debug("Loan Id: {} with installment {} due date {}", loan.getId(), installment.getInstallmentNumber(), installment.getDueDate()); outstandingAmount = outstandingAmount.add(installment.getTotalOutstanding(loanCurrency).getAmount()); if (!oldestOverdueInstallment) { log.debug("Oldest installment {} {}", installment.getInstallmentNumber(), installment.getDueDate()); + CollectionData overDueInstallmentDelinquentData = calculateDelinquencyDataForOverdueInstallment(loan, installment); + overdueSinceDate = overDueInstallmentDelinquentData.getDelinquentDate(); oldestOverdueInstallment = true; - overdueSinceDate = installment.getDueDate(); overdueSinceDateWasSet = true; - - amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); - - boolean isLatestInstallment = Objects.equals(installment.getId(), latestInstallment.getId()); - for (LoanTransaction loanTransaction : chargebackTransactions) { - boolean isLoanTransactionIsOnOrAfterInstallmentFromDate = loanTransaction.getTransactionDate().isEqual( - installment.getFromDate()) || loanTransaction.getTransactionDate().isAfter(installment.getFromDate()); - boolean isLoanTransactionIsBeforeNotLastInstallmentDueDate = !isLatestInstallment - && loanTransaction.getTransactionDate().isBefore(installment.getDueDate()); - boolean isLoanTransactionIsOnOrBeforeLastInstallmentDueDate = isLatestInstallment - && (loanTransaction.getTransactionDate().isEqual(installment.getDueDate()) - || loanTransaction.getTransactionDate().isBefore(installment.getDueDate())); - if (isLoanTransactionIsOnOrAfterInstallmentFromDate && (isLoanTransactionIsBeforeNotLastInstallmentDueDate - || isLoanTransactionIsOnOrBeforeLastInstallmentDueDate)) { - amountAvailable = amountAvailable.subtract(loanTransaction.getAmount()); - if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) { - overdueSinceDate = loanTransaction.getTransactionDate(); - break; - } - } - } } } else if (!firstNotYetDueInstallment) { log.debug("Loan Id: {} with installment {} due date {}", loan.getId(), installment.getInstallmentNumber(), installment.getDueDate()); firstNotYetDueInstallment = true; - amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); - log.debug("Amount available {}", amountAvailable); - for (LoanTransaction loanTransaction : chargebackTransactions) { - boolean isLoanTransactionIsOnOrAfterInstallmentFromDate = loanTransaction.getTransactionDate().isEqual( - installment.getFromDate()) || loanTransaction.getTransactionDate().isAfter(installment.getFromDate()); - boolean isLoanTransactionIsBeforeInstallmentDueDate = loanTransaction.getTransactionDate() - .isBefore(installment.getDueDate()); - boolean isLoanTransactionIsBeforeBusinessDate = loanTransaction.getTransactionDate().isBefore(businessDate); - if (isLoanTransactionIsOnOrAfterInstallmentFromDate && isLoanTransactionIsBeforeInstallmentDueDate - && isLoanTransactionIsBeforeBusinessDate) { - log.debug("Loan CB Transaction: {} {} {}", loanTransaction.getId(), loanTransaction.getTransactionDate(), - loanTransaction.getAmount()); - amountAvailable = amountAvailable.subtract(loanTransaction.getAmount()); - if (amountAvailable.compareTo(BigDecimal.ZERO) < 0 && !overdueSinceDateWasSet) { - overdueSinceDate = loanTransaction.getTransactionDate(); - overdueSinceDateWasSet = true; - } - } + CollectionData nonOverDueInstallmentDelinquentData = calculateDelinquencyDataForNonOverdueInstallment(loan, + installment); + outstandingAmount = outstandingAmount.add(nonOverDueInstallmentDelinquentData.getDelinquentAmount()); + if (!overdueSinceDateWasSet) { + overdueSinceDate = nonOverDueInstallmentDelinquentData.getDelinquentDate(); + overdueSinceDateWasSet = true; } + } + } + } + + Integer graceDays = 0; + if (loan.getLoanProductRelatedDetail().getGraceOnArrearsAgeing() != null) { + graceDays = loan.getLoanProductRelatedDetail().getGraceOnArrearsAgeing(); + } + log.debug("Loan id {} with overdue since date {} and outstanding amount {}", loan.getId(), overdueSinceDate, outstandingAmount); - if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) { - outstandingAmount = outstandingAmount.add(amountAvailable.abs()); + Long overdueDays = 0L; + if (overdueSinceDate != null) { + overdueDays = DateUtils.getDifferenceInDays(overdueSinceDate, businessDate); + if (overdueDays < 0) { + overdueDays = 0L; + } + collectionData.setPastDueDays(overdueDays); + overdueSinceDate = overdueSinceDate.plusDays(graceDays.longValue()); + collectionData.setDelinquentDate(overdueSinceDate); + } + collectionData.setDelinquentAmount(outstandingAmount); + collectionData.setDelinquentDays(0L); + Long delinquentDays = overdueDays - graceDays; + if (delinquentDays > 0) { + collectionData.setDelinquentDays(delinquentDays); + } + + log.debug("Result: {}", collectionData.toString()); + return collectionData; + } + + @Override + public LoanDelinquencyData getLoanDelinquencyData(final Loan loan) { + + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + LocalDate overdueSinceDate = null; + CollectionData collectionData = CollectionData.template(); + Map loanInstallmentsCollectionData = new HashMap<>(); + BigDecimal outstandingAmount = BigDecimal.ZERO; + boolean oldestOverdueInstallment = false; + boolean overdueSinceDateWasSet = false; + boolean firstNotYetDueInstallment = false; + log.debug("Loan id {} with {} installments", loan.getId(), loan.getRepaymentScheduleInstallments().size()); + for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { + CollectionData installmentCollectionData = CollectionData.template(); + if (!installment.isObligationsMet()) { + installmentCollectionData = getInstallmentOverdueCollectionData(loan, installment); + outstandingAmount = outstandingAmount.add(installmentCollectionData.getDelinquentAmount()); + // Get the oldest overdue installment if exists + if (DateUtils.isBefore(installment.getDueDate(), businessDate)) { + if (!oldestOverdueInstallment) { + overdueSinceDate = installmentCollectionData.getDelinquentDate(); + oldestOverdueInstallment = true; + overdueSinceDateWasSet = true; + } + } else if (!firstNotYetDueInstallment) { + firstNotYetDueInstallment = true; + if (!overdueSinceDateWasSet) { + overdueSinceDate = installmentCollectionData.getDelinquentDate(); + overdueSinceDateWasSet = true; } } } + // if installment level delinquency enabled add delinquency data for installment + if (loan.isEnableInstallmentLevelDelinquency()) { + loanInstallmentsCollectionData.put(installment.getId(), installmentCollectionData); + } + } Integer graceDays = 0; @@ -139,8 +167,116 @@ public CollectionData getOverdueCollectionData(final Loan loan) { if (delinquentDays > 0) { collectionData.setDelinquentDays(delinquentDays); } + return new LoanDelinquencyData(collectionData, loanInstallmentsCollectionData); + } - log.debug("Result: {}", collectionData.toString()); + private CollectionData getInstallmentOverdueCollectionData(final Loan loan, final LoanRepaymentScheduleInstallment installment) { + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + LocalDate overdueSinceDate = null; + CollectionData collectionData = CollectionData.template(); + BigDecimal outstandingAmount = BigDecimal.ZERO; + if (DateUtils.isBefore(installment.getDueDate(), businessDate)) { + // checking overdue installment delinquency data + CollectionData overDueInstallmentDelinquentData = calculateDelinquencyDataForOverdueInstallment(loan, installment); + outstandingAmount = outstandingAmount.add(overDueInstallmentDelinquentData.getDelinquentAmount()); + overdueSinceDate = overDueInstallmentDelinquentData.getDelinquentDate(); + + } else { + // checking non overdue installment for chargeback transactions before installment due date and before + // business date + CollectionData nonOverDueInstallmentDelinquentData = calculateDelinquencyDataForNonOverdueInstallment(loan, installment); + outstandingAmount = outstandingAmount.add(nonOverDueInstallmentDelinquentData.getDelinquentAmount()); + overdueSinceDate = nonOverDueInstallmentDelinquentData.getDelinquentDate(); + } + + // Grace days are not considered for installment level delinquency calculation currently. + + Long overdueDays = 0L; + if (overdueSinceDate != null) { + // TODO : Changes for considering paused delinquency days for overdue days calculation + overdueDays = DateUtils.getDifferenceInDays(overdueSinceDate, businessDate); + if (overdueDays < 0) { + overdueDays = 0L; + } + collectionData.setPastDueDays(overdueDays); + collectionData.setDelinquentDate(overdueSinceDate); + } + collectionData.setDelinquentAmount(outstandingAmount); + collectionData.setDelinquentDays(0L); + Long delinquentDays = overdueDays; + if (delinquentDays > 0) { + collectionData.setDelinquentDays(delinquentDays); + } + return collectionData; + + } + + private CollectionData calculateDelinquencyDataForOverdueInstallment(final Loan loan, + final LoanRepaymentScheduleInstallment installment) { + final MonetaryCurrency loanCurrency = loan.getCurrency(); + LoanRepaymentScheduleInstallment latestInstallment = loan.getLastLoanRepaymentScheduleInstallment(); + List chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback); + LocalDate overdueSinceDate = null; + CollectionData collectionData = CollectionData.template(); + BigDecimal outstandingAmount = BigDecimal.ZERO; + + outstandingAmount = outstandingAmount.add(installment.getTotalOutstanding(loanCurrency).getAmount()); + overdueSinceDate = installment.getDueDate(); + BigDecimal amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); + boolean isLatestInstallment = Objects.equals(installment.getId(), latestInstallment.getId()); + for (LoanTransaction loanTransaction : chargebackTransactions) { + boolean isLoanTransactionIsOnOrAfterInstallmentFromDate = DateUtils.isEqual(loanTransaction.getTransactionDate(), + installment.getFromDate()) || DateUtils.isAfter(loanTransaction.getTransactionDate(), installment.getFromDate()); + boolean isLoanTransactionIsBeforeNotLastInstallmentDueDate = !isLatestInstallment + && DateUtils.isBefore(loanTransaction.getTransactionDate(), installment.getDueDate()); + boolean isLoanTransactionIsOnOrBeforeLastInstallmentDueDate = isLatestInstallment + && (DateUtils.isEqual(loanTransaction.getTransactionDate(), installment.getDueDate()) + || DateUtils.isBefore(loanTransaction.getTransactionDate(), installment.getDueDate())); + if (isLoanTransactionIsOnOrAfterInstallmentFromDate + && (isLoanTransactionIsBeforeNotLastInstallmentDueDate || isLoanTransactionIsOnOrBeforeLastInstallmentDueDate)) { + amountAvailable = amountAvailable.subtract(loanTransaction.getAmount()); + if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) { + overdueSinceDate = loanTransaction.getTransactionDate(); + break; + } + } + } + collectionData.setDelinquentDate(overdueSinceDate); + collectionData.setDelinquentAmount(outstandingAmount); + return collectionData; + } + + private CollectionData calculateDelinquencyDataForNonOverdueInstallment(final Loan loan, + final LoanRepaymentScheduleInstallment installment) { + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + final MonetaryCurrency loanCurrency = loan.getCurrency(); + + LocalDate overdueSinceDate = null; + CollectionData collectionData = CollectionData.template(); + BigDecimal outstandingAmount = BigDecimal.ZERO; + + List chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback); + BigDecimal amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); + for (LoanTransaction loanTransaction : chargebackTransactions) { + + boolean isLoanTransactionIsOnOrAfterInstallmentFromDate = DateUtils.isEqual(loanTransaction.getTransactionDate(), + installment.getFromDate()) || DateUtils.isAfter(loanTransaction.getTransactionDate(), installment.getFromDate()); + boolean isLoanTransactionIsBeforeInstallmentDueDate = DateUtils.isBefore(loanTransaction.getTransactionDate(), + installment.getDueDate()); + boolean isLoanTransactionIsBeforeBusinessDate = DateUtils.isBefore(loanTransaction.getTransactionDate(), businessDate); + if (isLoanTransactionIsOnOrAfterInstallmentFromDate && isLoanTransactionIsBeforeInstallmentDueDate + && isLoanTransactionIsBeforeBusinessDate) { + amountAvailable = amountAvailable.subtract(loanTransaction.getAmount()); + if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) { + overdueSinceDate = loanTransaction.getTransactionDate(); + } + } + } + if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) { + outstandingAmount = outstandingAmount.add(amountAvailable.abs()); + } + collectionData.setDelinquentDate(overdueSinceDate); + collectionData.setDelinquentAmount(outstandingAmount); return collectionData; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java index 40131394bbf..88c27afa8c6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java @@ -23,6 +23,7 @@ import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; +import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository; import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper; import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper; import org.apache.fineract.portfolio.delinquency.mapper.LoanDelinquencyTagMapper; @@ -50,9 +51,11 @@ public DelinquencyReadPlatformService delinquencyReadPlatformService(Delinquency DelinquencyBucketRepository repositoryBucket, LoanDelinquencyTagHistoryRepository repositoryLoanDelinquencyTagHistory, DelinquencyRangeMapper mapperRange, DelinquencyBucketMapper mapperBucket, LoanDelinquencyTagMapper mapperLoanDelinquencyTagHistory, LoanRepository loanRepository, - LoanDelinquencyDomainService loanDelinquencyDomainService) { + LoanDelinquencyDomainService loanDelinquencyDomainService, + LoanInstallmentDelinquencyTagRepository repositoryLoanInstallmentDelinquencyTag) { return new DelinquencyReadPlatformServiceImpl(repositoryRange, repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange, - mapperBucket, mapperLoanDelinquencyTagHistory, loanRepository, loanDelinquencyDomainService); + mapperBucket, mapperLoanDelinquencyTagHistory, loanRepository, loanDelinquencyDomainService, + repositoryLoanInstallmentDelinquencyTag); } @Bean @@ -62,10 +65,11 @@ public DelinquencyWritePlatformService delinquencyWritePlatformService(Delinquen DelinquencyBucketRepository repositoryBucket, DelinquencyBucketMappingsRepository repositoryBucketMappings, LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository, LoanRepositoryWrapper loanRepository, LoanProductRepository loanProductRepository, BusinessEventNotifierService businessEventNotifierService, - LoanDelinquencyDomainService loanDelinquencyDomainService) { + LoanDelinquencyDomainService loanDelinquencyDomainService, + LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository) { return new DelinquencyWritePlatformServiceImpl(dataValidatorBucket, dataValidatorRange, repositoryRange, repositoryBucket, repositoryBucketMappings, loanDelinquencyTagRepository, loanRepository, loanProductRepository, businessEventNotifierService, - loanDelinquencyDomainService); + loanDelinquencyDomainService, loanInstallmentDelinquencyTagRepository); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/api/FloatingRatesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/api/FloatingRatesApiResourceSwagger.java index 81d5a88dfed..db4dfa2274c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/api/FloatingRatesApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/api/FloatingRatesApiResourceSwagger.java @@ -62,7 +62,7 @@ public static final class PostFloatingRatesResponse { private PostFloatingRatesResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "GetFloatingRatesResponse") @@ -71,7 +71,7 @@ public static final class GetFloatingRatesResponse { private GetFloatingRatesResponse() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Floating Rate 1") public String name; @Schema(example = "true") @@ -98,7 +98,7 @@ static final class GetFloatingRatesRatePeriods { private GetFloatingRatesRatePeriods() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Dec 15, 2015") public String fromDate; @Schema(example = "11") @@ -118,7 +118,7 @@ private GetFloatingRatesRatePeriods() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Floating Rate 1") public String name; @Schema(example = "true") @@ -163,7 +163,7 @@ private PutFloatingRatesChanges() {} } @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PutFloatingRatesChanges changes; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java index 932e48a42ed..0dcb7dca7c8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java @@ -20,6 +20,8 @@ import jakarta.persistence.PersistenceException; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -28,28 +30,17 @@ import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate; import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper; import org.apache.fineract.portfolio.floatingrates.serialization.FloatingRateDataValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor +@Slf4j public class FloatingRateWritePlatformServiceImpl implements FloatingRateWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(FloatingRateWritePlatformServiceImpl.class); private final FloatingRateDataValidator fromApiJsonDeserializer; private final FloatingRateRepositoryWrapper floatingRateRepository; - @Autowired - public FloatingRateWritePlatformServiceImpl(final FloatingRateDataValidator fromApiJsonDeserializer, - final FloatingRateRepositoryWrapper floatingRateRepository) { - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - this.floatingRateRepository = floatingRateRepository; - } - @Transactional @Override public CommandProcessingResult createFloatingRate(final JsonCommand command) { @@ -118,7 +109,7 @@ private void handleDataIntegrityIssues(final JsonCommand command, final Throwabl } private void logAsErrorUnexpectedDataIntegrityException(Exception dve) { - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java index 9eeeff0cea9..1c097cb188f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java @@ -25,27 +25,21 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData; import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData; import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData; import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class FloatingRatesReadPlatformServiceImpl implements FloatingRatesReadPlatformService { private final JdbcTemplate jdbcTemplate; - @Autowired - public FloatingRatesReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - @Override public List retrieveAll() { FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(false); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/starter/FloatingRatesConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/starter/FloatingRatesConfiguration.java new file mode 100644 index 00000000000..3bdc334937b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/floatingrates/starter/FloatingRatesConfiguration.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.floatingrates.starter; + +import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper; +import org.apache.fineract.portfolio.floatingrates.serialization.FloatingRateDataValidator; +import org.apache.fineract.portfolio.floatingrates.service.FloatingRateWritePlatformService; +import org.apache.fineract.portfolio.floatingrates.service.FloatingRateWritePlatformServiceImpl; +import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService; +import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class FloatingRatesConfiguration { + + @Bean + @ConditionalOnMissingBean(FloatingRatesReadPlatformService.class) + public FloatingRatesReadPlatformService floatingRatesReadPlatformService(JdbcTemplate jdbcTemplate) { + return new FloatingRatesReadPlatformServiceImpl(jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(FloatingRateWritePlatformService.class) + public FloatingRateWritePlatformService floatingRateWritePlatformService(FloatingRateDataValidator fromApiJsonDeserializer, + FloatingRateRepositoryWrapper floatingRateRepository) { + return new FloatingRateWritePlatformServiceImpl(fromApiJsonDeserializer, floatingRateRepository); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/api/FundsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/api/FundsApiResourceSwagger.java index 2dd3c8b7ba7..6b54488f256 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/api/FundsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/api/FundsApiResourceSwagger.java @@ -33,7 +33,7 @@ public static final class GetFundsResponse { private GetFundsResponse() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "EU Agri Fund") public String name; } @@ -53,7 +53,7 @@ public static final class PostFundsResponse { private PostFundsResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutFundsFundIdRequest") @@ -71,7 +71,7 @@ public static final class PutFundsFundIdResponse { private PutFundsFundIdResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PutFundsFundIdRequest changes; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformServiceImpl.java index 3d50ea52abd..e99c6e49fe5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformServiceImpl.java @@ -21,28 +21,21 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.fund.data.FundData; import org.apache.fineract.portfolio.fund.exception.FundNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class FundReadPlatformServiceImpl implements FundReadPlatformService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - @Autowired - public FundReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - } - private static final class FundMapper implements RowMapper { public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformServiceJpaRepositoryImpl.java index 20bab2f4c46..8750d70aae8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundWritePlatformServiceJpaRepositoryImpl.java @@ -20,6 +20,8 @@ import jakarta.persistence.PersistenceException; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -30,32 +32,19 @@ import org.apache.fineract.portfolio.fund.domain.FundRepository; import org.apache.fineract.portfolio.fund.exception.FundNotFoundException; import org.apache.fineract.portfolio.fund.serialization.FundCommandFromApiJsonDeserializer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@Slf4j +@RequiredArgsConstructor public class FundWritePlatformServiceJpaRepositoryImpl implements FundWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(FundWritePlatformServiceJpaRepositoryImpl.class); - private final PlatformSecurityContext context; private final FundCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final FundRepository fundRepository; - @Autowired - public FundWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final FundCommandFromApiJsonDeserializer fromApiJsonDeserializer, final FundRepository fundRepository) { - this.context = context; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - this.fundRepository = fundRepository; - } - @Transactional @Override @CacheEvict(value = "funds", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('fn')") @@ -123,7 +112,7 @@ private void handleFundDataIntegrityIssues(final JsonCommand command, final Thro "name", name); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.fund.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/starter/FundConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/starter/FundConfiguration.java new file mode 100644 index 00000000000..dfc9dae3a38 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/starter/FundConfiguration.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.fund.starter; + +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.fund.domain.FundRepository; +import org.apache.fineract.portfolio.fund.serialization.FundCommandFromApiJsonDeserializer; +import org.apache.fineract.portfolio.fund.service.FundReadPlatformService; +import org.apache.fineract.portfolio.fund.service.FundReadPlatformServiceImpl; +import org.apache.fineract.portfolio.fund.service.FundWritePlatformService; +import org.apache.fineract.portfolio.fund.service.FundWritePlatformServiceJpaRepositoryImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class FundConfiguration { + + @Bean + @ConditionalOnMissingBean(FundReadPlatformService.class) + public FundReadPlatformService fundReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context) { + return new FundReadPlatformServiceImpl(jdbcTemplate, context); + } + + @Bean + @ConditionalOnMissingBean(FundWritePlatformService.class) + public FundWritePlatformService fundWritePlatformService(PlatformSecurityContext context, + FundCommandFromApiJsonDeserializer fromApiJsonDeserializer, FundRepository fundRepository) { + return new FundWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, fundRepository); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResourceSwagger.java index b72c9222914..edfdb422193 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResourceSwagger.java @@ -39,7 +39,7 @@ static final class GetCentersOfficeOptions { private GetCentersOfficeOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Head Office") public String name; @Schema(example = "Head Office") @@ -51,7 +51,7 @@ static final class GetCentersStaffOptions { private GetCentersStaffOptions() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "D, Mary") public String displayName; } @@ -61,7 +61,7 @@ private GetCentersStaffOptions() {} @Schema(example = "[2013, 4, 18]") public LocalDate activationDate; @Schema(example = "2") - public Integer officeId; + public Long officeId; public Set officeOptions; public Set staffOptions; } @@ -80,7 +80,7 @@ static final class GetCentersStatus { private GetCentersStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "groupingStatusType.pending") public String code; @Schema(example = "Pending") @@ -88,14 +88,14 @@ private GetCentersStatus() {} } @Schema(example = "2") - public Integer id; + public Long id; public GetCentersStatus status; @Schema(example = "false") public Boolean active; @Schema(example = "Center 1") public String name; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; @Schema(example = ".2.") @@ -113,14 +113,14 @@ public static final class GetCentersCenterIdResponse { private GetCentersCenterIdResponse() {} @Schema(example = "8") - public Integer id; + public Long id; public GetCentersResponse.GetCentersPageItems.GetCentersStatus status; @Schema(example = "false") public Boolean active; @Schema(example = "First Center (No groups)") public String name; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; @Schema(example = ".8.") @@ -135,7 +135,7 @@ private PostCentersRequest() {} @Schema(example = "First Center (No groups)") public String name; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "false") public Boolean active; } @@ -146,11 +146,11 @@ public static final class PostCentersResponse { private PostCentersResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "8") - public Integer groupId; + public Long groupId; @Schema(example = "8") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutCentersCenterIdRequest") @@ -176,11 +176,11 @@ private PutCentersChanges() {} } @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "8") - public Integer groupId; + public Long groupId; @Schema(example = "8") - public Integer resourceId; + public Long resourceId; public PutCentersChanges changes; } @@ -195,7 +195,7 @@ private DeleteCentersChanges() {} } @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public DeleteCentersChanges changes; } @@ -205,7 +205,7 @@ public static final class PostCentersCenterIdRequest { private PostCentersCenterIdRequest() {} @Schema(example = "32") - public Integer closureReasonId; + public Long closureReasonId; @Schema(example = "05 May 2014") public String closureDate; @Schema(example = "en") @@ -220,7 +220,7 @@ public static final class PostCentersCenterIdResponse { private PostCentersCenterIdResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "GetCentersCenterIdAccountsResponse") @@ -237,7 +237,7 @@ static final class GetCentersCenterIdStatus { private GetCentersCenterIdStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "savingsAccountStatusType.submitted.and.pending.approval") public String code; @Schema(example = "Submitted and pending approval") @@ -287,7 +287,7 @@ static final class GetCentersAccountType { private GetCentersAccountType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountType.group") public String code; @Schema(example = "Group") @@ -313,7 +313,7 @@ static final class GetCentersDepositType { private GetCentersDepositType() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "depositAccountType.savingsDeposit") public String code; @Schema(example = "Savings") @@ -321,11 +321,11 @@ private GetCentersDepositType() {} } @Schema(example = "16") - public Integer id; + public Long id; @Schema(example = "000000016") public Long accountNo; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "Voluntary savings") public String productName; public GetCentersCenterIdStatus status; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResourceSwagger.java index 03a5e32c1c4..8f27280b306 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResourceSwagger.java @@ -39,7 +39,7 @@ static final class GetGroupsTemplateOfficeOptions { private GetGroupsTemplateOfficeOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Head Office") public String name; @Schema(example = "Head Office") @@ -51,7 +51,7 @@ static final class GetGroupsTemplateStaffOptions { private GetGroupsTemplateStaffOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "C, Mike") public String displayName; } @@ -61,11 +61,11 @@ static final class GetGroupsTemplateClientOptions { private GetGroupsTemplateClientOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Petra Yton") public String displayName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; } @@ -106,7 +106,7 @@ private GetGroupsTemplateColumnValues() {} } @Schema(example = "1") - public Integer officeId; + public Long officeId; public Set officeOptions; public Set staffOptions; public Set clientOptions; @@ -127,7 +127,7 @@ static final class GetGroupsStatus { private GetGroupsStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "clientStatusType.pending") public String code; @Schema(example = "Pending") @@ -135,14 +135,14 @@ private GetGroupsStatus() {} } @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "AnotherGroup") public String name; public GetGroupsStatus status; @Schema(example = "false") public Boolean active; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; @Schema(example = ".4.") @@ -174,13 +174,13 @@ private GetGroupsGroupIdTimeline() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "First Group") public String name; @Schema(example = "000-1A") public String externalId; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; @Schema(example = ".1.") @@ -194,7 +194,7 @@ public static final class PostGroupsRequest { private PostGroupsRequest() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Pending Group") public String name; @Schema(example = "false") @@ -207,11 +207,11 @@ public static final class PostGroupsResponse { private PostGroupsResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "2") - public Integer groupId; + public Long groupId; @Schema(example = "2") - public Integer resourceId; + public Long resourceId; } @Schema(description = "DeleteGroupsGroupIdResponse") @@ -220,11 +220,11 @@ public static final class DeleteGroupsGroupIdResponse { private DeleteGroupsGroupIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "2") - public Integer groupId; + public Long groupId; @Schema(example = "2") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PostGroupsGroupIdCommandUnassignStaffRequest") @@ -233,7 +233,7 @@ public static final class PostGroupsGroupIdCommandUnassignStaffRequest { private PostGroupsGroupIdCommandUnassignStaffRequest() {} @Schema(example = "1") - public Integer staffId; + public Long staffId; } @Schema(description = "PostGroupsGroupIdCommandUnassignStaffResponse") @@ -247,11 +247,11 @@ private PostGroupsGroupIdCommandUnassignStaffChanges() {} } @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer groupId; + public Long groupId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PostGroupsGroupIdCommandUnassignStaffChanges changes; } @@ -278,11 +278,11 @@ private PutGroupsGroupIdChanges() {} } @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer groupId; + public Long groupId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PutGroupsGroupIdChanges changes; } @@ -300,7 +300,7 @@ static final class GetGroupsGroupIdAccountsStatus { private GetGroupsGroupIdAccountsStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "loanStatusType.submitted.and.pending.approval") public String code; @Schema(example = "Submitted and pending approval") @@ -328,7 +328,7 @@ static final class GetGroupsGroupIdAccountsLoanType { private GetGroupsGroupIdAccountsLoanType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountType.group") public String code; @Schema(example = "Group") @@ -336,11 +336,11 @@ private GetGroupsGroupIdAccountsLoanType() {} } @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "000000003") public Long accountNo; @Schema(example = "3") - public Integer productId; + public Long productId; @Schema(example = "daily product") public String productName; public GetGroupsGroupIdAccountsStatus status; @@ -356,7 +356,7 @@ static final class GetGroupsGroupIdAccountsSavingStatus { private GetGroupsGroupIdAccountsSavingStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "savingsAccountStatusType.submitted.and.pending.approval") public String code; @Schema(example = "Submitted and pending approval") @@ -398,7 +398,7 @@ static final class GetGroupsGroupIdAccountsSavingAccountType { private GetGroupsGroupIdAccountsSavingAccountType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountType.group") public String code; @Schema(example = "Group") @@ -406,11 +406,11 @@ private GetGroupsGroupIdAccountsSavingAccountType() {} } @Schema(example = "9") - public Integer id; + public Long id; @Schema(example = "000000009") public Long accountNo; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "p_sav") public String productName; public GetGroupsGroupIdAccountsSavingStatus status; @@ -427,7 +427,7 @@ static final class GetGroupsGroupIdAccountsMemberLoanStatus { private GetGroupsGroupIdAccountsMemberLoanStatus() {} @Schema(example = "200") - public Integer id; + public Long id; @Schema(example = "loanStatusType.approved") public String code; @Schema(example = "Approved") @@ -455,7 +455,7 @@ static final class GetGroupsGroupIdAccountsMemberLoanType { private GetGroupsGroupIdAccountsMemberLoanType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "accountType.jlg") public String code; @Schema(example = "JLG") @@ -463,11 +463,11 @@ private GetGroupsGroupIdAccountsMemberLoanType() {} } @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "000000004") public Long accountNo; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "testLoan") public String productName; public GetGroupsGroupIdAccountsMemberLoanStatus status; @@ -479,11 +479,11 @@ static final class GetGroupsGroupIdAccountsMemberSavingsAccounts { private GetGroupsGroupIdAccountsMemberSavingsAccounts() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "000000003") public Long accountNo; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "p_sav") public String productName; public GetGroupsGroupIdAccountsSavingAccounts.GetGroupsGroupIdAccountsSavingStatus status; @@ -507,11 +507,11 @@ static final class PostGroupsGroupIdClients { private PostGroupsGroupIdClients() {} @Schema(example = "1") - public Integer id; + public Long id; } @Schema(example = "2") - public Integer destinationGroupId; + public Long destinationGroupId; public Set clients; } @@ -521,6 +521,6 @@ public static final class PostGroupsGroupIdResponse { private PostGroupsGroupIdResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java index d1119a45789..e1e9b1dc80b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java @@ -70,10 +70,8 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -@Service @RequiredArgsConstructor public class CenterReadPlatformServiceImpl implements CenterReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformServiceImpl.java index 525d9fbfb35..21f114c65e0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupLevelReadPlatformServiceImpl.java @@ -21,26 +21,19 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.group.data.GroupLevelData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class GroupLevelReadPlatformServiceImpl implements GroupLevelReadPlatformService { private final PlatformSecurityContext context; private final JdbcTemplate jdbcTemplate; - @Autowired - public GroupLevelReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - } - @Override public Collection retrieveAllLevels() { this.context.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java index 86f0486962e..204dda9698b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.core.data.PaginationParameters; @@ -49,14 +50,12 @@ import org.apache.fineract.portfolio.group.domain.GroupTypes; import org.apache.fineract.portfolio.group.exception.GroupNotFoundException; import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -@Service +@RequiredArgsConstructor public class GroupReadPlatformServiceImpl implements GroupReadPlatformService { public static final String ID = "id"; @@ -76,24 +75,6 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService { private final PaginationParametersDataValidator paginationParametersDataValidator; private final ColumnValidator columnValidator; - @Autowired - public GroupReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate, - final CenterReadPlatformService centerReadPlatformService, final OfficeReadPlatformService officeReadPlatformService, - final StaffReadPlatformService staffReadPlatformService, final CodeValueReadPlatformService codeValueReadPlatformService, - final PaginationParametersDataValidator paginationParametersDataValidator, final ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - this.centerReadPlatformService = centerReadPlatformService; - this.officeReadPlatformService = officeReadPlatformService; - this.staffReadPlatformService = staffReadPlatformService; - this.codeValueReadPlatformService = codeValueReadPlatformService; - this.paginationParametersDataValidator = paginationParametersDataValidator; - this.columnValidator = columnValidator; - this.paginationHelper = paginationHelper; - this.sqlGenerator = sqlGenerator; - } - @Override public GroupGeneralData retrieveTemplate(final Long officeId, final boolean isCenterGroup, final boolean staffInSelectedOfficeOnly) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformServiceImpl.java index 196919bb244..eb86c8e1da3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesReadPlatformServiceImpl.java @@ -21,29 +21,22 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.group.data.GroupRoleData; import org.apache.fineract.portfolio.group.exception.GroupRoleNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class GroupRolesReadPlatformServiceImpl implements GroupRolesReadPlatformService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - @Autowired - public GroupRolesReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - } - @Override public Collection retrieveGroupRoles(final Long groupId) { this.context.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformServiceJpaRepositoryImpl.java index 7fc544d99ad..014161de528 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupRolesWritePlatformServiceJpaRepositoryImpl.java @@ -19,6 +19,8 @@ package org.apache.fineract.portfolio.group.service; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -35,19 +37,14 @@ import org.apache.fineract.portfolio.group.domain.GroupRoleRepositoryWrapper; import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException; import org.apache.fineract.portfolio.group.serialization.GroupRolesDataValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.NonTransientDataAccessException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; -@Service +@Slf4j +@RequiredArgsConstructor public class GroupRolesWritePlatformServiceJpaRepositoryImpl implements GroupRolesWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(GroupRolesWritePlatformServiceJpaRepositoryImpl.class); - private final PlatformSecurityContext context; private final GroupRepositoryWrapper groupRepository; private final GroupRolesDataValidator fromApiJsonDeserializer; @@ -55,19 +52,6 @@ public class GroupRolesWritePlatformServiceJpaRepositoryImpl implements GroupRol private final ClientRepositoryWrapper clientRepository; private final GroupRoleRepositoryWrapper groupRoleRepository; - @Autowired - public GroupRolesWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final GroupRepositoryWrapper groupRepository, final GroupRolesDataValidator fromApiJsonDeserializer, - final CodeValueRepositoryWrapper codeValueRepository, final ClientRepositoryWrapper clientRepository, - final GroupRoleRepositoryWrapper groupRoleRepository) { - this.context = context; - this.groupRepository = groupRepository; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - this.codeValueRepository = codeValueRepository; - this.clientRepository = clientRepository; - this.groupRoleRepository = groupRoleRepository; - } - @Override public CommandProcessingResult createRole(final JsonCommand command) { @@ -111,7 +95,7 @@ private void handleGroupDataIntegrityIssues(final JsonCommand command, final Thr GroupingTypesApiConstants.clientIdParamName, roleId, clientId, command.getGroupId()); } - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java index e59d2e1f54f..0c327def9e5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java @@ -88,12 +88,10 @@ import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; -@Service @Slf4j @RequiredArgsConstructor public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements GroupingTypesWritePlatformService { @@ -153,7 +151,7 @@ private CommandProcessingResult createGroupingType(final JsonCommand command, fi final boolean active = command.booleanPrimitiveValueOfParameterNamed(GroupingTypesApiConstants.activeParamName); LocalDate submittedOnDate = DateUtils.getBusinessLocalDate(); - if (active && submittedOnDate.isAfter(activationDate)) { + if (active && DateUtils.isAfter(submittedOnDate, activationDate)) { submittedOnDate = activationDate; } if (command.hasParameter(GroupingTypesApiConstants.submittedOnDateParamName)) { @@ -616,7 +614,7 @@ private void validateLoansAndSavingsForGroupOrCenterClose(final Group groupOrCen final String errorMessage = groupOrCenter.getGroupLevel().getLevelName() + " cannot be closed because of non-closed loans."; throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close", "loan.not.closed", errorMessage); - } else if (loanStatus.isClosed() && loan.getClosedOnDate().isAfter(closureDate)) { + } else if (loanStatus.isClosed() && DateUtils.isAfter(loan.getClosedOnDate(), closureDate)) { final String errorMessage = groupOrCenter.getGroupLevel().getLevelName() + "closureDate cannot be before the loan closedOnDate."; throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close", @@ -640,7 +638,7 @@ private void validateLoansAndSavingsForGroupOrCenterClose(final Group groupOrCen + " cannot be closed with active savings accounts associated."; throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close", "savings.account.not.closed", errorMessage); - } else if (saving.isClosed() && saving.getClosedOnDate().isAfter(closureDate)) { + } else if (saving.isClosed() && DateUtils.isAfter(saving.getClosedOnDate(), closureDate)) { final String errorMessage = groupOrCenter.getGroupLevel().getLevelName() + " closureDate cannot be before the loan closedOnDate."; throw new InvalidGroupStateTransitionException(groupOrCenter.getGroupLevel().getLevelName(), "close", @@ -906,7 +904,7 @@ private void validateForJLGSavings(final Long groupId, final Set clientM public void validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(final Office groupOffice, final GroupLevel groupLevel, final LocalDate activationDate) { - if (activationDate != null && groupOffice.getOpeningLocalDate().isAfter(activationDate)) { + if (activationDate != null && DateUtils.isAfter(groupOffice.getOpeningLocalDate(), activationDate)) { final String levelName = groupLevel.getLevelName(); final String errorMessage = levelName + " activation date should be greater than or equal to the parent Office's creation date " + activationDate.toString(); @@ -916,10 +914,7 @@ public void validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(final Offic } private void checkGroupMembersMeetingSyncWithCenterMeeting(final Long centerId, final Set groupMembers) { - - /** - * Get parent(center) calendar - */ + // Get parent(center) calendar Calendar ceneterCalendar = null; final CalendarInstance parentCalendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId( centerId, CalendarEntityType.CENTERS.getValue(), CalendarType.COLLECTION.getValue()); @@ -928,9 +923,7 @@ private void checkGroupMembersMeetingSyncWithCenterMeeting(final Long centerId, } for (final Group group : groupMembers) { - /** - * Get child(group) calendar - */ + // Get child(group) calendar Calendar groupCalendar = null; final CalendarInstance groupCalendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId( group.getId(), CalendarEntityType.GROUPS.getValue(), CalendarType.COLLECTION.getValue()); @@ -938,19 +931,14 @@ private void checkGroupMembersMeetingSyncWithCenterMeeting(final Long centerId, groupCalendar = groupCalendarInstance.getCalendar(); } - /** - * Group shouldn't have a meeting when no meeting attached for center - */ + // Group shouldn't have a meeting when no meeting attached for center if (ceneterCalendar == null && groupCalendar != null) { throw new GeneralPlatformDomainRuleException( "error.msg.center.associating.group.not.allowed.with.meeting.attached.to.group", "Group with id " + group.getId() + " is already associated with meeting", group.getId()); } - /** - * Group meeting recurrence should match with center meeting recurrence - */ + // Group meeting recurrence should match with center meeting recurrence else if (ceneterCalendar != null && groupCalendar != null) { - if (!ceneterCalendar.getRecurrence().equalsIgnoreCase(groupCalendar.getRecurrence())) { throw new GeneralPlatformDomainRuleException("error.msg.center.associating.group.not.allowed.with.different.meeting", "Group with id " + group.getId() + " meeting recurrence doesnot matched with center meeting recurrence", diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/starter/GroupConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/starter/GroupConfiguration.java new file mode 100644 index 00000000000..18ee752ccd3 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/starter/GroupConfiguration.java @@ -0,0 +1,138 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.group.starter; + +import org.apache.fineract.commands.service.CommandProcessingService; +import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper; +import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; +import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper; +import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; +import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; +import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; +import org.apache.fineract.portfolio.group.domain.GroupLevelRepository; +import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; +import org.apache.fineract.portfolio.group.domain.GroupRoleRepositoryWrapper; +import org.apache.fineract.portfolio.group.serialization.GroupRolesDataValidator; +import org.apache.fineract.portfolio.group.serialization.GroupingTypesDataValidator; +import org.apache.fineract.portfolio.group.service.CenterReadPlatformService; +import org.apache.fineract.portfolio.group.service.CenterReadPlatformServiceImpl; +import org.apache.fineract.portfolio.group.service.GroupLevelReadPlatformService; +import org.apache.fineract.portfolio.group.service.GroupLevelReadPlatformServiceImpl; +import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; +import org.apache.fineract.portfolio.group.service.GroupReadPlatformServiceImpl; +import org.apache.fineract.portfolio.group.service.GroupRolesReadPlatformService; +import org.apache.fineract.portfolio.group.service.GroupRolesReadPlatformServiceImpl; +import org.apache.fineract.portfolio.group.service.GroupRolesWritePlatformService; +import org.apache.fineract.portfolio.group.service.GroupRolesWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformService; +import org.apache.fineract.portfolio.group.service.GroupingTypesWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class GroupConfiguration { + + @Bean + @ConditionalOnMissingBean(CenterReadPlatformService.class) + public CenterReadPlatformService centerReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context, + ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService, + StaffReadPlatformService staffReadPlatformService, CodeValueReadPlatformService codeValueReadPlatformService, + ConfigurationDomainService configurationDomainService, ColumnValidator columnValidator, PaginationHelper paginationHelper, + DatabaseSpecificSQLGenerator sqlGenerator, PaginationParametersDataValidator paginationParametersDataValidator) { + return new CenterReadPlatformServiceImpl(jdbcTemplate, context, clientReadPlatformService, officeReadPlatformService, + staffReadPlatformService, codeValueReadPlatformService, configurationDomainService, columnValidator, paginationHelper, + sqlGenerator, paginationParametersDataValidator); + } + + @Bean + @ConditionalOnMissingBean(GroupingTypesWritePlatformService.class) + public GroupingTypesWritePlatformService groupingTypesWritePlatformService(PlatformSecurityContext context, + GroupRepositoryWrapper groupRepository, ClientRepositoryWrapper clientRepositoryWrapper, + OfficeRepositoryWrapper officeRepositoryWrapper, StaffRepositoryWrapper staffRepository, NoteRepository noteRepository, + GroupLevelRepository groupLevelRepository, GroupingTypesDataValidator fromApiJsonDeserializer, + LoanRepositoryWrapper loanRepositoryWrapper, CodeValueRepositoryWrapper codeValueRepository, + CommandProcessingService commandProcessingService, CalendarInstanceRepository calendarInstanceRepository, + ConfigurationDomainService configurationDomainService, SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, + AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, AccountNumberGenerator accountNumberGenerator, + EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, + BusinessEventNotifierService businessEventNotifierService + + ) { + return new GroupingTypesWritePlatformServiceJpaRepositoryImpl(context, groupRepository, clientRepositoryWrapper, + officeRepositoryWrapper, staffRepository, noteRepository, groupLevelRepository, fromApiJsonDeserializer, + loanRepositoryWrapper, codeValueRepository, commandProcessingService, calendarInstanceRepository, + configurationDomainService, savingsAccountRepositoryWrapper, accountNumberFormatRepository, accountNumberGenerator, + entityDatatableChecksWritePlatformService, businessEventNotifierService + + ); + } + + @Bean + @ConditionalOnMissingBean(GroupLevelReadPlatformService.class) + public GroupLevelReadPlatformService groupLevelReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate) { + return new GroupLevelReadPlatformServiceImpl(context, jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(GroupReadPlatformService.class) + public GroupReadPlatformService groupReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context, + OfficeReadPlatformService officeReadPlatformService, StaffReadPlatformService staffReadPlatformService, + CenterReadPlatformService centerReadPlatformService, CodeValueReadPlatformService codeValueReadPlatformService, + PaginationHelper paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator, + PaginationParametersDataValidator paginationParametersDataValidator, ColumnValidator columnValidator) { + return new GroupReadPlatformServiceImpl(jdbcTemplate, context, officeReadPlatformService, staffReadPlatformService, + centerReadPlatformService, codeValueReadPlatformService, paginationHelper, sqlGenerator, paginationParametersDataValidator, + columnValidator); + } + + @Bean + @ConditionalOnMissingBean(GroupRolesReadPlatformService.class) + public GroupRolesReadPlatformService groupRolesReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context) { + return new GroupRolesReadPlatformServiceImpl(jdbcTemplate, context); + } + + @Bean + @ConditionalOnMissingBean(GroupRolesWritePlatformService.class) + public GroupRolesWritePlatformService groupRolesWritePlatformService(PlatformSecurityContext context, + GroupRepositoryWrapper groupRepository, GroupRolesDataValidator fromApiJsonDeserializer, + CodeValueRepositoryWrapper codeValueRepository, ClientRepositoryWrapper clientRepository, + GroupRoleRepositoryWrapper groupRoleRepository) { + return new GroupRolesWritePlatformServiceJpaRepositoryImpl(context, groupRepository, fromApiJsonDeserializer, codeValueRepository, + clientRepository, groupRoleRepository); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartSlabsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartSlabsApiResourceSwagger.java index 2cbe318f2a2..d3b1c9ab1f8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartSlabsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartSlabsApiResourceSwagger.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.interestratechart.api; import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; import java.util.Set; /** @@ -42,7 +43,7 @@ static final class GetInterestRateChartsChartIdChartSlabsEntityType { private GetInterestRateChartsChartIdChartSlabsEntityType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "InterestIncentiveEntityType.customer") public Integer code; @Schema(example = "Customer") @@ -54,7 +55,7 @@ static final class GetInterestRateChartsChartIdChartSlabsAttributeName { private GetInterestRateChartsChartIdChartSlabsAttributeName() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "InterestIncentiveAttributeName.gender") public Integer code; @Schema(example = "Gender") @@ -66,7 +67,7 @@ static final class GetInterestRateChartsChartIdChartSlabsConditionType { private GetInterestRateChartsChartIdChartSlabsConditionType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "incentiveConditionType.equal") public Integer code; @Schema(example = "equal") @@ -78,7 +79,7 @@ static final class GetInterestRateChartsChartIdChartSlabsIncentiveType { private GetInterestRateChartsChartIdChartSlabsIncentiveType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "InterestIncentiveType.incentive") public Integer code; @Schema(example = "Incentive") @@ -86,7 +87,7 @@ private GetInterestRateChartsChartIdChartSlabsIncentiveType() {} } @Schema(example = "1") - public Integer id; + public Long id; public GetInterestRateChartsChartIdChartSlabsEntityType entityType; public GetInterestRateChartsChartIdChartSlabsAttributeName attributeName; public GetInterestRateChartsChartIdChartSlabsConditionType conditionType; @@ -100,7 +101,7 @@ private GetInterestRateChartsChartIdChartSlabsIncentiveType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "5% interest from 1 day till 180 days of deposit") public String description; public InterestRateChartsApiResourceSwagger.GetInterestRateChartsTemplateResponse.GetInterestRateChartsTemplatePeriodTypes periodTypes; @@ -130,11 +131,11 @@ private PostInterestRateChartsChartIdChartSlabsIncentives() {} @Schema(example = "2") public Integer conditionType; @Schema(example = "11") - public Integer attributeValue; + public String attributeValue; @Schema(example = "2") public Integer incentiveType; @Schema(example = "-1") - public Float amount; + public BigDecimal amount; } @Schema(example = "0") @@ -158,7 +159,7 @@ public static final class PostInterestRateChartsChartIdChartSlabsResponse { private PostInterestRateChartsChartIdChartSlabsResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutInterestRateChartsChartIdChartSlabsChartSlabIdRequest") @@ -178,7 +179,7 @@ public static final class PutInterestRateChartsChartIdChartSlabsChartSlabIdRespo private PutInterestRateChartsChartIdChartSlabsChartSlabIdResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PutInterestRateChartsChartIdChartSlabsChartSlabIdRequest changes; } @@ -188,6 +189,6 @@ public static final class DeleteInterestRateChartsChartIdChartSlabsResponse { private DeleteInterestRateChartsChartIdChartSlabsResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartsApiResourceSwagger.java index b7811aaf6d0..b0feeca8e67 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/api/InterestRateChartsApiResourceSwagger.java @@ -59,7 +59,7 @@ static final class GetInterestRateChartsChartSlabs { private GetInterestRateChartsChartSlabs() {} @Schema(example = "1") - public Integer id; + public Long id; public GetInterestRateChartsTemplateResponse.GetInterestRateChartsTemplatePeriodTypes periodTypes; @Schema(example = "1") public Integer fromPeriod; @@ -87,11 +87,11 @@ private GetInterestRateChartsCurrency() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "[2014, 1, 1]") public LocalDate fromDate; @Schema(example = "1") - public Integer savingsProductId; + public Long savingsProductId; @Schema(example = "Fixed Deposit Product 001") public String savingsProductName; public Set chartSlabs; @@ -122,7 +122,7 @@ public static final class PostInterestRateChartsResponse { private PostInterestRateChartsResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutInterestRateChartsChartIdRequest") @@ -142,7 +142,7 @@ public static final class PutInterestRateChartsChartIdResponse { private PutInterestRateChartsChartIdResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "DeleteInterestRateChartsChartIdResponse") @@ -151,6 +151,6 @@ public static final class DeleteInterestRateChartsChartIdResponse { private DeleteInterestRateChartsChartIdResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartData.java index f18083d29cc..6dd4d9ce84c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartData.java @@ -23,6 +23,7 @@ import java.util.Collection; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.service.DateUtils; /** * Immutable data object representing a InterestRateChart. @@ -143,7 +144,7 @@ public void addChartSlab(final InterestRateChartSlabData chartSlab) { } public boolean isFromDateAfter(final LocalDate compareDate) { - return (compareDate == null) ? false : this.fromDate.isAfter(compareDate); + return compareDate != null && DateUtils.isAfter(this.fromDate, compareDate); } public LocalDate endDate() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartDataValidator.java index 5c8e222ef12..cd30a0544ea 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/data/InterestRateChartDataValidator.java @@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.interestratechart.InterestRateChartApiConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -115,10 +116,8 @@ public void validateForCreate(final String json, final DataValidatorBuilder base isPrimaryGroupingByAmount = false; } - if (fromDate != null && toDate != null) { - if (fromDate.isAfter(toDate)) { - baseDataValidator.parameter(fromDateParamName).value(fromDate).failWithCode("from.date.is.after.to.date"); - } + if (toDate != null && DateUtils.isAfter(fromDate, toDate)) { + baseDataValidator.parameter(fromDateParamName).value(fromDate).failWithCode("from.date.is.after.to.date"); } // validate chart Slabs - mandatory when creating @@ -177,11 +176,8 @@ public void validateForUpdate(final String json, final DataValidatorBuilder base if (isPrimaryGroupingByAmount == null) { isPrimaryGroupingByAmount = false; } - - if (fromDate != null && toDate != null) { - if (fromDate.isAfter(toDate)) { - baseDataValidator.parameter(fromDateParamName).value(fromDate).failWithCode("from.date.is.after.to.date"); - } + if (toDate != null && DateUtils.isAfter(fromDate, toDate)) { + baseDataValidator.parameter(fromDateParamName).value(fromDate).failWithCode("from.date.is.after.to.date"); } validateChartSlabs(element, baseDataValidator, isPrimaryGroupingByAmount); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java index 22887192e9f..974aea51007 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java @@ -230,8 +230,7 @@ private void validateCharts(final DataValidatorBuilder baseDataValidator, final if (!existingChart.equals(this)) { if (this.chartFields.isOverlapping(existingChart.chartFields)) { baseDataValidator.failWithCodeNoParameterAddedToErrorCode("chart.overlapping.from.and.end.dates", - existingChart.getFromDateAsLocalDate(), existingChart.getEndDateAsLocalDate(), this.getFromDateAsLocalDate(), - this.getEndDateAsLocalDate()); + existingChart.getFromDate(), existingChart.getEndDate(), this.getFromDate(), this.getEndDate()); } } } @@ -318,12 +317,12 @@ private boolean removeChartSlab(InterestRateChartSlab chartSlab) { return chartSlabs.remove(chartSlab); } - public LocalDate getFromDateAsLocalDate() { - return this.chartFields.getFromDateAsLocalDate(); + public LocalDate getFromDate() { + return this.chartFields.getFromDate(); } - public LocalDate getEndDateAsLocalDate() { - return this.chartFields.getEndDateAsLocalDate(); + public LocalDate getEndDate() { + return this.chartFields.getEndDate(); } private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java index ce1e68d6ed8..e1a78a96dc3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java @@ -88,7 +88,7 @@ public void update(JsonCommand command, final Map actualChanges, final String localeAsInput = command.locale(); final String dateFormat = command.dateFormat(); - if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateAsLocalDate())) { + if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDate())) { final String newValueAsString = command.stringValueOfParameterNamed(fromDateParamName); actualChanges.put(fromDateParamName, newValueAsString); actualChanges.put(localeParamName, localeAsInput); @@ -96,7 +96,7 @@ public void update(JsonCommand command, final Map actualChanges, this.fromDate = command.localDateValueOfParameterNamed(fromDateParamName); } - if (command.isChangeInLocalDateParameterNamed(endDateParamName, getEndDateAsLocalDate())) { + if (command.isChangeInLocalDateParameterNamed(endDateParamName, getEndDate())) { final String newValueAsString = command.stringValueOfParameterNamed(endDateParamName); actualChanges.put(endDateParamName, newValueAsString); actualChanges.put(localeParamName, localeAsInput); @@ -116,31 +116,27 @@ public void update(JsonCommand command, final Map actualChanges, } public boolean isFromDateAfterToDate() { - return isFromDateAfter(getEndDateAsLocalDate()); + return isFromDateAfter(getEndDate()); } public boolean isFromDateAfter(LocalDate compare) { - final LocalDate fromDate = getFromDateAsLocalDate(); - if (fromDate != null && compare != null) { - return fromDate.isAfter(compare); - } - return false; + return compare != null && DateUtils.isAfter(getFromDate(), compare); } - public LocalDate getFromDateAsLocalDate() { + public LocalDate getFromDate() { return this.fromDate; } - public LocalDate getEndDateAsLocalDate() { + public LocalDate getEndDate() { return this.endDate; } public boolean isOverlapping(InterestRateChartFields that) { - final LocalDate thisFromDate = this.getFromDateAsLocalDate(); - LocalDate thisEndDate = this.getEndDateAsLocalDate(); + final LocalDate thisFromDate = this.getFromDate(); + LocalDate thisEndDate = this.getEndDate(); thisEndDate = thisEndDate == null ? DateUtils.getBusinessLocalDate() : thisEndDate; - final LocalDate thatFromDate = that.getFromDateAsLocalDate(); - LocalDate thatEndDate = that.getEndDateAsLocalDate(); + final LocalDate thatFromDate = that.getFromDate(); + LocalDate thatEndDate = that.getEndDate(); thatEndDate = thatEndDate == null ? DateUtils.getBusinessLocalDate() : thatEndDate; final LocalDateInterval thisInterval = LocalDateInterval.create(thisFromDate, thisEndDate); @@ -153,8 +149,8 @@ public boolean isOverlapping(InterestRateChartFields that) { } public boolean isApplicableChartFor(final LocalDate target) { - final LocalDate endDate = this.endDate == null ? DateUtils.getBusinessLocalDate() : this.getEndDateAsLocalDate(); - final LocalDateInterval interval = LocalDateInterval.create(getFromDateAsLocalDate(), endDate); + final LocalDate endDate = this.endDate == null ? DateUtils.getBusinessLocalDate() : this.getEndDate(); + final LocalDateInterval interval = LocalDateInterval.create(getFromDate(), endDate); return interval.contains(target); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java index d6ee0237b34..a1b7868d853 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java @@ -312,10 +312,7 @@ public boolean isIntegerSame(final Integer obj1, final Integer obj2) { public boolean isBigDecimalSame(final BigDecimal obj1, final BigDecimal obj2) { if (obj1 == null || obj2 == null) { - if (Objects.compare(obj1, obj2, Comparator.nullsFirst(Comparator.naturalOrder())) == 0 ? Boolean.TRUE : Boolean.FALSE) { - return true; - } - return false; + return Objects.compare(obj1, obj2, Comparator.nullsFirst(Comparator.naturalOrder())) == 0; } return obj1.compareTo(obj2) == 0; } @@ -326,7 +323,7 @@ public boolean isBetweenPeriod(final LocalDate periodStartDate, final LocalDate } public boolean isAmountRangeProvided() { - return (this.amountRangeFrom == null) ? false : true; + return this.amountRangeFrom != null; } public BigDecimal annualInterestRate() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveAssembler.java index 55e2f7a7e7e..36933b49bf0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentiveAssembler.java @@ -36,6 +36,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; @@ -43,19 +44,12 @@ import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentives; import org.apache.fineract.portfolio.interestratechart.domain.InterestIncentivesFields; import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class InterestIncentiveAssembler { private final FromJsonHelper fromApiJsonHelper; - @Autowired - public InterestIncentiveAssembler(final FromJsonHelper fromApiJsonHelper) { - this.fromApiJsonHelper = fromApiJsonHelper; - } - public Collection assembleIncentivesFrom(final JsonElement element, InterestRateChartSlab interestRateChartSlab, final Locale locale) { final Collection interestIncentivesSet = new HashSet<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesDropdownReadPlatformServiceImpl.java index 1430b6cbb7d..28879b5ca28 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestIncentivesDropdownReadPlatformServiceImpl.java @@ -19,15 +19,15 @@ package org.apache.fineract.portfolio.interestratechart.service; import java.util.Collection; +import lombok.NoArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.common.domain.ConditionType; import org.apache.fineract.portfolio.common.service.CommonEnumerations; import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName; import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveEntityType; import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveType; -import org.springframework.stereotype.Service; -@Service +@NoArgsConstructor public class InterestIncentivesDropdownReadPlatformServiceImpl implements InterestIncentiveDropdownReadPlatformService { @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartAssembler.java index 2d55e8c098a..b4031c38f3e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartAssembler.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; @@ -40,25 +41,14 @@ import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartFields; import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartRepositoryWrapper; import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class InterestRateChartAssembler { private final FromJsonHelper fromApiJsonHelper; private final InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper; private final InterestRateChartSlabAssembler chartSlabAssembler; - @Autowired - public InterestRateChartAssembler(final FromJsonHelper fromApiJsonHelper, - final InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper, - final InterestRateChartSlabAssembler chartSlabAssembler) { - this.fromApiJsonHelper = fromApiJsonHelper; - this.interestRateChartRepositoryWrapper = interestRateChartRepositoryWrapper; - this.chartSlabAssembler = chartSlabAssembler; - } - /** * Assembles a new {@link InterestRateChart} from JSON Slabs passed in request */ diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformServiceImpl.java index bcbc72ea148..384845a596c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartDropdownReadPlatformServiceImpl.java @@ -19,11 +19,11 @@ package org.apache.fineract.portfolio.interestratechart.service; import java.util.Collection; +import lombok.NoArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; -import org.springframework.stereotype.Service; -@Service +@NoArgsConstructor public class InterestRateChartDropdownReadPlatformServiceImpl implements InterestRateChartDropdownReadPlatformService { @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformServiceImpl.java index 66951630f89..99c504250f8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartReadPlatformServiceImpl.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.core.data.EnumOptionData; @@ -40,17 +41,12 @@ import org.apache.fineract.portfolio.interestratechart.exception.InterestRateChartNotFoundException; import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName; import org.apache.fineract.portfolio.savings.data.DepositProductData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; - -import lombok.extern.slf4j.Slf4j; -@Service @Slf4j public class InterestRateChartReadPlatformServiceImpl implements InterestRateChartReadPlatformService { @@ -62,7 +58,6 @@ public class InterestRateChartReadPlatformServiceImpl implements InterestRateCha private final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService; private final CodeValueReadPlatformService codeValueReadPlatformService; - @Autowired public InterestRateChartReadPlatformServiceImpl(PlatformSecurityContext context, final JdbcTemplate jdbcTemplate, InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService, final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabAssembler.java index eef49be9b88..46b9cbe8bc7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabAssembler.java @@ -38,6 +38,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; @@ -49,25 +50,14 @@ import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlabFields; import org.apache.fineract.portfolio.interestratechart.exception.InterestRateChartSlabNotFoundException; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class InterestRateChartSlabAssembler { private final FromJsonHelper fromApiJsonHelper; private final InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper; private final InterestIncentiveAssembler incentiveAssembler; - @Autowired - public InterestRateChartSlabAssembler(final FromJsonHelper fromApiJsonHelper, - final InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper, - final InterestIncentiveAssembler incentiveAssembler) { - this.fromApiJsonHelper = fromApiJsonHelper; - this.interestRateChartRepositoryWrapper = interestRateChartRepositoryWrapper; - this.incentiveAssembler = incentiveAssembler; - } - /** * Assembles a new {@link InterestRateChartSlab} from JSON Slabs passed in request */ diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformServiceImpl.java index d263e1cf381..a18269e79c8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabReadPlatformServiceImpl.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.core.data.EnumOptionData; @@ -37,14 +38,13 @@ import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabData; import org.apache.fineract.portfolio.interestratechart.exception.InterestRateChartSlabNotFoundException; import org.apache.fineract.portfolio.interestratechart.incentive.InterestIncentiveAttributeName; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor + public class InterestRateChartSlabReadPlatformServiceImpl implements InterestRateChartSlabReadPlatformService { private final PlatformSecurityContext context; @@ -53,21 +53,6 @@ public class InterestRateChartSlabReadPlatformServiceImpl implements InterestRat private final InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService; private final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService; private final CodeValueReadPlatformService codeValueReadPlatformService; - private final DatabaseSpecificSQLGenerator sqlGenerator; - - @Autowired - public InterestRateChartSlabReadPlatformServiceImpl(PlatformSecurityContext context, final JdbcTemplate jdbcTemplate, - InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService, - final InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService, - final CodeValueReadPlatformService codeValueReadPlatformService, DatabaseSpecificSQLGenerator sqlGenerator) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - this.chartDropdownReadPlatformService = chartDropdownReadPlatformService; - this.interestIncentiveDropdownReadPlatformService = interestIncentiveDropdownReadPlatformService; - this.codeValueReadPlatformService = codeValueReadPlatformService; - this.sqlGenerator = sqlGenerator; - chartSlabExtractor = new InterestRateChartSlabExtractor(sqlGenerator); - } @Override public Collection retrieveAll(Long chartId) { @@ -187,7 +172,7 @@ public InterestRateChartSlabData mapRow(ResultSet rs, @SuppressWarnings("unused" } - private static final class InterestRateChartSlabExtractor implements ResultSetExtractor> { + public static final class InterestRateChartSlabExtractor implements ResultSetExtractor> { InterestRateChartSlabsMapper chartSlabsMapper; InterestIncentiveMapper incentiveMapper = new InterestIncentiveMapper(); @@ -198,7 +183,7 @@ public String schema() { return this.schemaSql; } - private InterestRateChartSlabExtractor(DatabaseSpecificSQLGenerator sqlGenerator) { + public InterestRateChartSlabExtractor(DatabaseSpecificSQLGenerator sqlGenerator) { chartSlabsMapper = new InterestRateChartSlabsMapper(sqlGenerator); this.schemaSql = chartSlabsMapper.schema(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.java index da096ad57b2..c1e0547dc27 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.java @@ -21,6 +21,7 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; @@ -30,17 +31,11 @@ import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlab; import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlabRepository; import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl implements InterestRateChartSlabWritePlatformService { - @SuppressWarnings("unused") - private static final Logger LOG = LoggerFactory.getLogger(InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl.class); @SuppressWarnings("unused") private final PlatformSecurityContext context; private final InterestRateChartSlabDataValidator interestRateChartSlabDataValidator; @@ -53,22 +48,6 @@ public class InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl implemen @SuppressWarnings("unused") private final SavingsProductRepository savingsProductRepository; - @Autowired - public InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl(PlatformSecurityContext context, - final InterestRateChartSlabDataValidator interestRateChartSlabDataValidator, - final InterestRateChartAssembler interestRateChartAssembler, - final InterestRateChartRepositoryWrapper interestRateChartRepository, final SavingsProductRepository savingsProductRepository, - final InterestRateChartSlabRepository chartSlabRepository, - final InterestRateChartSlabAssembler interestRateChartSlabAssembler) { - this.context = context; - this.interestRateChartSlabDataValidator = interestRateChartSlabDataValidator; - this.interestRateChartAssembler = interestRateChartAssembler; - this.interestRateChartRepository = interestRateChartRepository; - this.savingsProductRepository = savingsProductRepository; - this.chartSlabRepository = chartSlabRepository; - this.interestRateChartSlabAssembler = interestRateChartSlabAssembler; - } - @Override @Transactional public CommandProcessingResult create(JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformServiceJpaRepositoryImpl.java index eb8f2b7b75c..92023a28e3d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/service/InterestRateChartWritePlatformServiceJpaRepositoryImpl.java @@ -20,6 +20,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; @@ -27,34 +28,17 @@ import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartDataValidator; import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart; import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartRepositoryWrapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class InterestRateChartWritePlatformServiceJpaRepositoryImpl implements InterestRateChartWritePlatformService { - @SuppressWarnings("unused") - private static final Logger LOG = LoggerFactory.getLogger(InterestRateChartWritePlatformServiceJpaRepositoryImpl.class); @SuppressWarnings("unused") private final PlatformSecurityContext context; private final InterestRateChartDataValidator interestRateChartDataValidator; private final InterestRateChartAssembler interestRateChartAssembler; private final InterestRateChartRepositoryWrapper interestRateChartRepository; - @Autowired - public InterestRateChartWritePlatformServiceJpaRepositoryImpl(PlatformSecurityContext context, - final InterestRateChartDataValidator interestRateChartDataValidator, - final InterestRateChartAssembler interestRateChartAssembler, - final InterestRateChartRepositoryWrapper interestRateChartRepository) { - this.context = context; - this.interestRateChartDataValidator = interestRateChartDataValidator; - this.interestRateChartAssembler = interestRateChartAssembler; - this.interestRateChartRepository = interestRateChartRepository; - } - @Override @Transactional public CommandProcessingResult create(JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/starter/InterestRateChartConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/starter/InterestRateChartConfiguration.java new file mode 100644 index 00000000000..3c18b2327f6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/starter/InterestRateChartConfiguration.java @@ -0,0 +1,144 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.interestratechart.starter; + +import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartDataValidator; +import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabDataValidator; +import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartRepositoryWrapper; +import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlabRepository; +import org.apache.fineract.portfolio.interestratechart.service.InterestIncentiveAssembler; +import org.apache.fineract.portfolio.interestratechart.service.InterestIncentiveDropdownReadPlatformService; +import org.apache.fineract.portfolio.interestratechart.service.InterestIncentivesDropdownReadPlatformServiceImpl; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartDropdownReadPlatformService; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartDropdownReadPlatformServiceImpl; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformServiceImpl; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabAssembler; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabReadPlatformService; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabReadPlatformServiceImpl; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabWritePlatformService; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartWritePlatformService; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class InterestRateChartConfiguration { + + @Bean + @ConditionalOnMissingBean(InterestIncentiveAssembler.class) + public InterestIncentiveAssembler interestIncentiveAssembler(FromJsonHelper fromApiJsonHelper) { + return new InterestIncentiveAssembler(fromApiJsonHelper); + } + + @Bean + @ConditionalOnMissingBean(InterestIncentiveDropdownReadPlatformService.class) + public InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService( + + ) { + return new InterestIncentivesDropdownReadPlatformServiceImpl( + + ); + } + + @Bean + @ConditionalOnMissingBean(InterestRateChartAssembler.class) + public InterestRateChartAssembler interestRateChartAssembler(FromJsonHelper fromApiJsonHelper, + InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper, InterestRateChartSlabAssembler chartSlabAssembler) { + return new InterestRateChartAssembler(fromApiJsonHelper, interestRateChartRepositoryWrapper, chartSlabAssembler); + } + + @Bean + @ConditionalOnMissingBean(InterestRateChartDropdownReadPlatformService.class) + public InterestRateChartDropdownReadPlatformService interestRateChartDropdownReadPlatformService( + + ) { + return new InterestRateChartDropdownReadPlatformServiceImpl( + + ); + } + + @Bean + @ConditionalOnMissingBean(InterestRateChartReadPlatformService.class) + public InterestRateChartReadPlatformService interestRateChartReadPlatformService(PlatformSecurityContext context, + JdbcTemplate jdbcTemplate, InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService, + InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService, + CodeValueReadPlatformService codeValueReadPlatformService, DatabaseSpecificSQLGenerator sqlGenerator) { + return new InterestRateChartReadPlatformServiceImpl(context, jdbcTemplate, chartDropdownReadPlatformService, + interestIncentiveDropdownReadPlatformService, codeValueReadPlatformService, sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(InterestRateChartSlabAssembler.class) + public InterestRateChartSlabAssembler interestRateChartSlabAssembler(FromJsonHelper fromApiJsonHelper, + InterestRateChartRepositoryWrapper interestRateChartRepositoryWrapper, InterestIncentiveAssembler incentiveAssembler) { + return new InterestRateChartSlabAssembler(fromApiJsonHelper, interestRateChartRepositoryWrapper, incentiveAssembler); + } + + @Bean + public InterestRateChartSlabReadPlatformServiceImpl.InterestRateChartSlabExtractor interestRateChartSlabExtractor( + DatabaseSpecificSQLGenerator sqlGenerator) { + return new InterestRateChartSlabReadPlatformServiceImpl.InterestRateChartSlabExtractor(sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(InterestRateChartSlabReadPlatformService.class) + public InterestRateChartSlabReadPlatformService interestRateChartSlabReadPlatformService( + + PlatformSecurityContext context, JdbcTemplate jdbcTemplate, + InterestRateChartSlabReadPlatformServiceImpl.InterestRateChartSlabExtractor chartSlabExtractor, + InterestRateChartDropdownReadPlatformService chartDropdownReadPlatformService, + InterestIncentiveDropdownReadPlatformService interestIncentiveDropdownReadPlatformService, + CodeValueReadPlatformService codeValueReadPlatformService) { + return new InterestRateChartSlabReadPlatformServiceImpl(context, jdbcTemplate, chartSlabExtractor, chartDropdownReadPlatformService, + interestIncentiveDropdownReadPlatformService, codeValueReadPlatformService); + } + + @Bean + @ConditionalOnMissingBean(InterestRateChartSlabWritePlatformService.class) + public InterestRateChartSlabWritePlatformService interestRateChartSlabWritePlatformService( + + PlatformSecurityContext context, InterestRateChartSlabDataValidator interestRateChartSlabDataValidator, + InterestRateChartAssembler interestRateChartAssembler, InterestRateChartSlabAssembler interestRateChartSlabAssembler, + InterestRateChartRepositoryWrapper interestRateChartRepository, InterestRateChartSlabRepository chartSlabRepository, + SavingsProductRepository savingsProductRepository) { + return new InterestRateChartSlabWritePlatformServiceJpaRepositoryImpl(context, interestRateChartSlabDataValidator, + interestRateChartAssembler, interestRateChartSlabAssembler, interestRateChartRepository, chartSlabRepository, + savingsProductRepository); + } + + @Bean + @ConditionalOnMissingBean(InterestRateChartWritePlatformService.class) + public InterestRateChartWritePlatformService interestRateChartWritePlatformService(PlatformSecurityContext context, + InterestRateChartDataValidator interestRateChartDataValidator, InterestRateChartAssembler interestRateChartAssembler, + InterestRateChartRepositoryWrapper interestRateChartRepository) { + return new InterestRateChartWritePlatformServiceJpaRepositoryImpl(context, interestRateChartDataValidator, + interestRateChartAssembler, interestRateChartRepository); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java index a3662c79106..6e75fa02a94 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java @@ -88,8 +88,8 @@ public String getLoanAuditFields(@Context final UriInfo uriInfo, @PathParam("loa final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId); Map auditFields = new HashMap<>( - Map.of(CREATED_BY, loan.getCreatedBy().orElse(null), CREATED_DATE, loan.getCreatedDate().orElse(null), LAST_MODIFIED_BY, - loan.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, loan.getLastModifiedDate().orElse(null))); + Map.of(CREATED_BY, loan.getCreatedBy().orElse(null), CREATED_DATE, loan.getCreatedDateTime(), LAST_MODIFIED_BY, + loan.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, loan.getLastModifiedDateTime())); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializerForMap.serialize(settings, auditFields); } @@ -108,8 +108,8 @@ public String getLoanTransactionAuditFields(@Context final UriInfo uriInfo, @Pat final LoanTransaction transaction = loanTransactionRepository.findById(transactionId).orElseThrow(); Map auditFields = new HashMap<>(Map.of(CREATED_BY, transaction.getCreatedBy().orElse(null), CREATED_DATE, - transaction.getCreatedDate().orElse(null), LAST_MODIFIED_BY, transaction.getLastModifiedBy().orElse(null), - LAST_MODIFIED_DATE, transaction.getLastModifiedDate().orElse(null))); + transaction.getCreatedDateTime(), LAST_MODIFIED_BY, transaction.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, + transaction.getLastModifiedDateTime())); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializerForMap.serialize(settings, auditFields); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanScheduleApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanScheduleApiResourceSwagger.java index 8d6edb9d3a9..9a3f46bea81 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanScheduleApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanScheduleApiResourceSwagger.java @@ -48,7 +48,7 @@ private PostLoanChanges() {} } @Schema(example = "1") - public Integer loanId; + public Long loanId; public PostLoanChanges changes; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java index d71e4659a20..33ed6e2b066 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java @@ -276,7 +276,7 @@ private PostLoansLoanIdTransactionsRequest() {} @Schema(example = "ban123") public String bankNumber; @Schema(example = "3") - public Integer loanChargeId; + public Long loanChargeId; @Schema(example = "28 June 2022") public String dueDate; @Schema(example = "1") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index f558eebbfc8..a011f28f63c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -63,7 +63,7 @@ static final class GetLoansTemplateProductOptions { private GetLoansTemplateProductOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Kampala Product (with cash accounting)") public String name; } @@ -73,7 +73,7 @@ private GetLoansTemplateProductOptions() {} @Schema(example = "Kampala first Client") public String clientName; @Schema(example = "2") - public Integer clientOfficeId; + public Long clientOfficeId; public GetLoansTemplateTimeline timeline; public Set productOptions; } @@ -88,7 +88,7 @@ static final class GetLoansLoanIdStatus { private GetLoansLoanIdStatus() {} @Schema(example = "300") - public Integer id; + public Long id; @Schema(example = "loanStatusType.active") public String code; @Schema(example = "Active") @@ -116,7 +116,7 @@ static final class GetLoansLoanIdLoanType { private GetLoansLoanIdLoanType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "loanType.individual") public String code; @Schema(example = "Individual") @@ -146,7 +146,7 @@ static final class GetLoansLoanIdTermPeriodFrequencyType { private GetLoansLoanIdTermPeriodFrequencyType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "termFrequency.periodFrequencyType.months") public String code; @Schema(example = "Months") @@ -158,7 +158,7 @@ static final class GetLoansLoanIdRepaymentFrequencyType { private GetLoansLoanIdRepaymentFrequencyType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "repaymentFrequency.periodFrequencyType.months") public String code; @Schema(example = "Months") @@ -170,7 +170,7 @@ static final class GetLoansLoanIdInterestRateFrequencyType { private GetLoansLoanIdInterestRateFrequencyType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "interestRateFrequency.periodFrequencyType.years") public String code; @Schema(example = "Per year") @@ -182,7 +182,7 @@ static final class GetLoansLoanIdAmortizationType { private GetLoansLoanIdAmortizationType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "amortizationType.equal.installments") public String code; @Schema(example = "Equal installments") @@ -194,7 +194,7 @@ static final class GetLoansLoanIdInterestType { private GetLoansLoanIdInterestType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "interestType.flat") public String code; @Schema(example = "Flat") @@ -206,7 +206,7 @@ static final class GetLoansLoanIdInterestCalculationPeriodType { private GetLoansLoanIdInterestCalculationPeriodType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "interestCalculationPeriodType.same.as.repayment.period") public String code; @Schema(example = "Same as repayment period") @@ -375,6 +375,8 @@ private GetLoansLoanIdRepaymentPeriod() {} public Double totalInstallmentAmountForPeriod; @Schema(example = "2.000000") public Double totalCredits; + @Schema(example = "true") + public Boolean downPaymentPeriod; } static final class GetLoansLoanIdDisbursementDetails { @@ -382,7 +384,7 @@ static final class GetLoansLoanIdDisbursementDetails { private GetLoansLoanIdDisbursementDetails() {} @Schema(example = "71") - public Integer id; + public Long id; @Schema(example = "[2022, 07, 01]") public LocalDate expectedDisbursementDate; @Schema(example = "[2022, 07, 01]") @@ -419,7 +421,7 @@ static final class GetLoansLoanIdLinkedAccount { private GetLoansLoanIdLinkedAccount() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "000000001") public String accountNo; } @@ -433,7 +435,7 @@ static final class GetLoansLoanIdChargeTimeType { private GetLoansLoanIdChargeTimeType() {} @Schema(example = "9") - public Integer id; + public Long id; @Schema(example = "chargeTimeType.overdueInstallment") public String code; @Schema(example = "overdue fees") @@ -445,7 +447,7 @@ static final class GetLoansLoanIdChargeCalculationType { private GetLoansLoanIdChargeCalculationType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "chargeCalculationType.percent.of.amount") public String code; @Schema(example = "% Amount") @@ -457,7 +459,7 @@ static final class GetLoansLoanIdChargePaymentMode { private GetLoansLoanIdChargePaymentMode() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "chargepaymentmode.regular") public String code; @Schema(example = "Regular") @@ -469,7 +471,7 @@ static final class GetLoansLoanIdFeeFrequency { private GetLoansLoanIdFeeFrequency() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "feeFrequencyperiodFrequencyType.weeks") public String code; @Schema(example = "Weeks") @@ -477,7 +479,7 @@ private GetLoansLoanIdFeeFrequency() {} } @Schema(example = "20") - public Integer id; + public Long id; @Schema(example = "overdraft penality") public String name; @Schema(example = "true") @@ -840,7 +842,7 @@ private GetLoansLoanIdLoanTransactionRelation() {} @Schema(example = "[2022, 07, 01]") public LocalDate transactionDate; @Schema(example = "101") - public Long paymentTypeId; + public Integer paymentTypeId; @Schema(example = "acct123") public String accountNumber; @Schema(example = "10001") @@ -998,25 +1000,25 @@ private GetLoansLoanIdCollectionData() {} @Schema(example = "false") public boolean disallowExpectedDisbursements; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "5e77989e-aa11-11bc-b109-0242ac120004") public String clientExternalId; @Schema(example = "Kampala first Client") public String clientName; @Schema(example = "2") - public Integer clientOfficeId; + public Long clientOfficeId; @Schema(example = "1") - public Integer loanProductId; + public Long loanProductId; @Schema(example = "Kampala Product (with cash accounting)") public String loanProductName; @Schema(example = "Typical Kampala loan product with cash accounting enabled for testing.") public String loanProductDescription; @Schema(example = "22") - public Integer loanPurposeId; + public Long loanPurposeId; @Schema(example = "option.HousingImprovement") public String loanPurposeName; @Schema(example = "2") - public Integer loanOfficerId; + public Long loanOfficerId; @Schema(example = "LoanOfficer, Kampala") public String loanOfficerName; public GetLoansLoanIdLoanType loanType; @@ -1065,6 +1067,8 @@ private GetLoansLoanIdCollectionData() {} public GetDelinquencyRangesResponse delinquencyRange; @Schema(example = "false") public Boolean fraud; + @Schema(example = "false") + public Boolean enableInstallmentLevelDelinquency; @Schema(example = "250.000000") public Double totalOverpaid; public LocalDate lastClosedBusinessDate; @@ -1227,9 +1231,9 @@ private PostLoansRepaymentSchedulePeriods() {} public Long totalOutstanding; public Set periods; @Schema(example = "2") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") public Long loanId; @Schema(example = "1") @@ -1382,9 +1386,9 @@ private PutLoansLoanIdChanges() {} } @Schema(example = "2") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") public Long loanId; @Schema(example = "1") @@ -1400,9 +1404,9 @@ public static final class DeleteLoansLoanIdResponse { private DeleteLoansLoanIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") public Long loanId; @Schema(example = "1") @@ -1473,7 +1477,7 @@ static final class PostLoansLoanIdStatus { private PostLoansLoanIdStatus() {} @Schema(example = "300") - public Integer id; + public Long id; @Schema(example = "loanStatusType.approved") public String code; @Schema(example = "Approved") 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 037f0758f64..10562615952 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 @@ -253,6 +253,7 @@ public class LoanAccountData { private LocalDate overpaidOnDate; private CollectionData delinquent; private DelinquencyRangeData delinquencyRange; + private Boolean enableInstallmentLevelDelinquency; private LocalDate lastClosedBusinessDate; private Boolean chargedOff; @@ -663,7 +664,7 @@ public static LoanAccountData basicLoanDetails(final Long id, final String accou final DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud, LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, - final boolean disableScheduleExtensionForDownPayment) { + final boolean disableScheduleExtensionForDownPayment, final boolean enableInstallmentLevelDelinquency) { final CollectionData delinquent = CollectionData.template(); @@ -706,7 +707,8 @@ public static LoanAccountData basicLoanDetails(final Long id, final String accou .setLastClosedBusinessDate(lastClosedBusinessDate).setOverpaidOnDate(overpaidOnDate).setChargedOff(chargedOff) .setEnableDownPayment(enableDownPayment).setDisbursedAmountPercentageForDownPayment(disbursedAmountPercentageForDownPayment) .setEnableAutoRepaymentForDownPayment(enableAutoRepaymentForDownPayment) - .setDisableScheduleExtensionForDownPayment(disableScheduleExtensionForDownPayment); + .setDisableScheduleExtensionForDownPayment(disableScheduleExtensionForDownPayment) + .setEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency); } /* @@ -794,7 +796,8 @@ public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, .setChargedOff(acc.chargedOff).setEnableDownPayment(acc.enableDownPayment) .setDisbursedAmountPercentageForDownPayment(acc.disbursedAmountPercentageForDownPayment) .setEnableAutoRepaymentForDownPayment(acc.enableAutoRepaymentForDownPayment) - .setDisableScheduleExtensionForDownPayment(acc.disableScheduleExtensionForDownPayment); + .setDisableScheduleExtensionForDownPayment(acc.disableScheduleExtensionForDownPayment) + .setEnableInstallmentLevelDelinquency(acc.enableInstallmentLevelDelinquency); } public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection productOptions, @@ -869,7 +872,8 @@ public static LoanAccountData associateGroup(final LoanAccountData acc, final Gr .setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled) .setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent) .setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements) - .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff); + .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff) + .setEnableInstallmentLevelDelinquency(acc.enableInstallmentLevelDelinquency); } public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map memberLoanCycle) { @@ -966,7 +970,8 @@ public static LoanAccountData associateMemberVariations(final LoanAccountData ac .setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled) .setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent) .setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements) - .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff); + .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff) + .setEnableInstallmentLevelDelinquency(acc.enableInstallmentLevelDelinquency); } public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData, @@ -1031,7 +1036,8 @@ public static LoanAccountData withInterestRecalculationCalendarData(final LoanAc .setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled) .setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent) .setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements) - .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff); + .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff) + .setEnableInstallmentLevelDelinquency(acc.enableInstallmentLevelDelinquency); } public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) { @@ -1089,7 +1095,8 @@ public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, fi .setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled) .setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent) .setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements) - .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff); + .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff) + .setEnableInstallmentLevelDelinquency(acc.enableInstallmentLevelDelinquency); } public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) { @@ -1150,7 +1157,8 @@ public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, fi .setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled) .setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent) .setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements) - .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff); + .setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff) + .setEnableInstallmentLevelDelinquency(acc.enableInstallmentLevelDelinquency); } public static final Comparator ClientNameComparator = (loan1, loan2) -> { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentDataComparator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanDelinquencyData.java similarity index 66% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentDataComparator.java rename to fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanDelinquencyData.java index 91236636099..3efaea390c1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentDataComparator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanDelinquencyData.java @@ -16,15 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.portfolio.loanaccount.domain; +package org.apache.fineract.portfolio.loanaccount.data; -import java.util.Comparator; -import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; -public class LoanRepaymentDataComparator implements Comparator { +@AllArgsConstructor +@ToString +@Getter +@Setter +public class LoanDelinquencyData { - @Override - public int compare(final LoanTransactionData o1, final LoanTransactionData o2) { - return o2.getDate().compareTo(o1.getDate()); - } + private CollectionData loanCollectionData; + private Map loanInstallmentsCollectionData; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 604e5804f6d..7b04162305f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -369,7 +369,7 @@ public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, f final Integer transactionType, Integer installmentNumber) { boolean isAccountTransfer = true; checkClientOrGroupActive(loan); - if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loan.getId() + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -465,7 +465,7 @@ public LoanTransaction makeRefund(final Long accountId, final CommandProcessingR boolean isAccountTransfer = true; final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); checkClientOrGroupActive(loan); - if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loan.getId() + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -518,7 +518,7 @@ public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDat final PaymentDetail paymentDetail, final String noteText, final ExternalId txnExternalId, final boolean isLoanToLoanTransfer) { final Loan loan = this.loanAccountAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); - if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loan.getId() + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -551,7 +551,7 @@ public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDat @Override public void reverseTransfer(final LoanTransaction loanTransaction) { if (loanTransaction.getLoan().isChargedOff() - && loanTransaction.getTransactionDate().isBefore(loanTransaction.getLoan().getChargedOffOnDate())) { + && DateUtils.isBefore(loanTransaction.getTransactionDate(), loanTransaction.getLoan().getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan transaction: " + loanTransaction.getId() + " reversal is not allowed before or on the date when the loan got charged-off", @@ -614,10 +614,10 @@ public void recalculateAccruals(Loan loan, boolean isInterestCalculationHappened Set loanCharges = loan.getActiveCharges(); for (LoanRepaymentScheduleInstallment installment : installments) { - if (installment.getDueDate().isAfter(loan.getMaturityDate())) { + if (DateUtils.isAfter(installment.getDueDate(), loan.getMaturityDate())) { accruedTill = DateUtils.getBusinessLocalDate(); } - if (!isOrganisationDateEnabled || organisationStartDate.isBefore(installment.getDueDate())) { + if (!isOrganisationDateEnabled || DateUtils.isBefore(organisationStartDate, installment.getDueDate())) { generateLoanScheduleAccrualData(accruedTill, loanScheduleAccrualList, loanId, officeId, accrualStartDate, repaymentFrequency, repayEvery, interestCalculatedFrom, loanProductId, currency, currencyData, loanCharges, installment); @@ -641,12 +641,12 @@ private void generateLoanScheduleAccrualData(final LocalDate accruedTill, final LocalDate interestCalculatedFrom, final Long loanProductId, final MonetaryCurrency currency, final CurrencyData currencyData, final Set loanCharges, final LoanRepaymentScheduleInstallment installment) { - if (!accruedTill.isBefore(installment.getDueDate()) - || (accruedTill.isAfter(installment.getFromDate()) && !accruedTill.isAfter(installment.getDueDate()))) { + if (!DateUtils.isBefore(accruedTill, installment.getDueDate()) || (DateUtils.isAfter(accruedTill, installment.getFromDate()) + && !DateUtils.isAfter(accruedTill, installment.getDueDate()))) { BigDecimal dueDateFeeIncome = BigDecimal.ZERO; BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO; LocalDate chargesTillDate = installment.getDueDate(); - if (!accruedTill.isAfter(installment.getDueDate())) { + if (!DateUtils.isAfter(accruedTill, installment.getDueDate())) { chargesTillDate = accruedTill; } @@ -685,7 +685,7 @@ private void updateLoanTransaction(final Long loanTransactionId, final LoanTrans @Override public LoanTransaction creditBalanceRefund(final Loan loan, final LocalDate transactionDate, final BigDecimal transactionAmount, final String noteText, final ExternalId externalId, PaymentDetail paymentDetail) { - if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loan.getId() + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -729,7 +729,7 @@ public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessing final List existingReversedTransactionIds = new ArrayList<>(); final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount); - if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loan.getId() + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -768,7 +768,7 @@ public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessing @Override public LoanTransaction foreCloseLoan(Loan loan, final LocalDate foreClosureDate, final String noteText, final ExternalId externalId, Map changes) { - if (loan.isChargedOff() && foreClosureDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(foreClosureDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loan.getId() + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -785,7 +785,7 @@ public LoanTransaction foreCloseLoan(Loan loan, final LocalDate foreClosureDate, final ScheduleGeneratorDTO scheduleGeneratorDTO = null; final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate); if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() - && (loan.getAccruedTill() == null || !foreClosureDate.isEqual(loan.getAccruedTill()))) { + && (loan.getAccruedTill() == null || !DateUtils.isEqual(foreClosureDate, loan.getAccruedTill()))) { loan.reverseAccrualsAfter(foreClosureDate); Money[] accruedReceivables = loan.getReceivableIncome(foreClosureDate); Money interestPortion = foreCloseDetail.getInterestCharged(currency).minus(accruedReceivables[0]); @@ -805,7 +805,7 @@ public LoanTransaction foreCloseLoan(Loan loan, final LocalDate foreClosureDate, loan.addLoanTransaction(accrualTransaction); Set accrualCharges = accrualTransaction.getLoanChargesPaid(); for (LoanCharge loanCharge : loan.getActiveCharges()) { - boolean isDue = fromDate.isEqual(loan.getDisbursementDate()) + boolean isDue = DateUtils.isEqual(fromDate, loan.getDisbursementDate()) ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, foreClosureDate) : loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, foreClosureDate); if (loanCharge.isActive() && !loanCharge.isPaid() && (isDue || loanCharge.isInstalmentFee())) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationsComparator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationsComparator.java index 8f7b3b5d7a6..95d4d09d4b7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationsComparator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationsComparator.java @@ -29,10 +29,8 @@ public class LoanTermVariationsComparator implements Comparator installments, final LocalDate transactionDate) { final LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex); - return transactionDate.isBefore(currentInstallment.getDueDate()); + return DateUtils.isBefore(transactionDate, currentInstallment.getDueDate()); } /** @@ -325,7 +326,7 @@ protected boolean isTransactionInAdvanceOfInstallment(final int installmentIndex protected boolean isTransactionALateRepaymentOnInstallment(final int installmentIndex, final List installments, final LocalDate transactionDate) { final LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex); - return transactionDate.isAfter(currentInstallment.getDueDate()); + return DateUtils.isAfter(transactionDate, currentInstallment.getDueDate()); } private void recalculateChargeOffTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction, @@ -387,6 +388,13 @@ protected void reprocessInstallments(List inst if (lastInstallment.isAdditional() && lastInstallment.getDue(currency).isZero()) { installments.remove(lastInstallment); } + // TODO: rewrite and handle it at the proper place when disbursement handling got fixed + for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { + if (loanRepaymentScheduleInstallment.getTotalOutstanding(currency).isGreaterThanZero()) { + loanRepaymentScheduleInstallment.updateObligationMet(false); + loanRepaymentScheduleInstallment.updateObligationMetOnDate(null); + } + } } private void recalculateCreditTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction, @@ -477,7 +485,7 @@ private void processCreditTransaction(LoanTransaction loanTransaction, Money ove LocalDate pastDueDate = null; for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { pastDueDate = currentInstallment.getDueDate(); - if (!currentInstallment.isAdditional() && currentInstallment.getDueDate().isAfter(transactionDate)) { + if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) { currentInstallment.addToCredits(transactionAmount.getAmount()); currentInstallment.addToPrincipal(transactionDate, transactionAmount); if (repaidAmount.isGreaterThanZero()) { @@ -491,7 +499,7 @@ private void processCreditTransaction(LoanTransaction loanTransaction, Money ove // If already exists an additional installment just update the due date and // principal from the Loan chargeback / CBR transaction } else if (currentInstallment.isAdditional()) { - if (transactionDate.isAfter(currentInstallment.getDueDate())) { + if (DateUtils.isAfter(transactionDate, currentInstallment.getDueDate())) { currentInstallment.updateDueDate(transactionDate); } @@ -669,18 +677,19 @@ protected LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set ch if (loanCharge.isInstalmentFee()) { LoanInstallmentCharge paidLoanChargePerInstallment = loanCharge .getLastPaidOrPartiallyPaidInstallmentLoanCharge(currency); - if (chargePerInstallment == null - || (paidLoanChargePerInstallment != null && chargePerInstallment.getRepaymentInstallment().getDueDate() - .isBefore(paidLoanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { + if (chargePerInstallment == null || (paidLoanChargePerInstallment != null + && DateUtils.isBefore(chargePerInstallment.getRepaymentInstallment().getDueDate(), + paidLoanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { installemntCharge = loanCharge; chargePerInstallment = paidLoanChargePerInstallment; } } else if (latestPaidCharge == null || (loanCharge.isPaidOrPartiallyPaid(currency) - && loanCharge.getDueLocalDate().isAfter(latestPaidCharge.getDueLocalDate()))) { + && DateUtils.isAfter(loanCharge.getDueLocalDate(), latestPaidCharge.getDueLocalDate()))) { latestPaidCharge = loanCharge; } } } if (latestPaidCharge == null || (chargePerInstallment != null - && latestPaidCharge.getDueLocalDate().isAfter(chargePerInstallment.getRepaymentInstallment().getDueDate()))) { + && DateUtils.isAfter(latestPaidCharge.getDueLocalDate(), chargePerInstallment.getRepaymentInstallment().getDueDate()))) { latestPaidCharge = installemntCharge; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 6a9c9d8a5bc..7ab5a8a91ac 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -19,40 +19,46 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl; import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.ObjectUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy; import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; +import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; -import org.springframework.context.annotation.Profile; +import org.jetbrains.annotations.NotNull; -//TODO: remove `test` profile when it is finished -@Profile("test") @Slf4j public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor { public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy"; + private final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeProcessor = new SingleLoanChargeRepaymentScheduleProcessingWrapper(); + @Override public String getCode() { return ADVANCED_PAYMENT_ALLOCATION_STRATEGY; @@ -91,10 +97,44 @@ protected Money handleRefundTransactionPaymentOfInstallment(LoanRepaymentSchedul throw new NotImplementedException(); } + private void processSingleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, + List installments, Set charges, + ChangedTransactionDetail changedTransactionDetail) { + if (loanTransaction.getId() == null) { + processLatestTransaction(loanTransaction, currency, installments, charges, Money.zero(currency)); + loanTransaction.adjustInterestComponent(currency); + } else { + /* + * For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties) has + * changed.
+ */ + final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); + + // Reset derived component of new loan transaction and + // re-process transaction + processLatestTransaction(newLoanTransaction, currency, installments, charges, Money.zero(currency)); + newLoanTransaction.adjustInterestComponent(currency); + /* + * Check if the transaction amounts have changed. If so, reverse the original transaction and update + * changedTransactionDetail accordingly + */ + if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) { + loanTransaction.updateLoanTransactionToRepaymentScheduleMappings( + newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings()); + } else { + createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail); + } + } + } + + private void processSingleCharge(LoanCharge loanCharge, MonetaryCurrency currency, List installments, + LocalDate disbursementDate) { + loanChargeProcessor.reprocess(currency, disbursementDate, installments, loanCharge); + } + @Override - public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, - List transactionsPostDisbursement, MonetaryCurrency currency, - List installments, Set charges) { + public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List loanTransactions, + MonetaryCurrency currency, List installments, Set charges) { // TODO: rewrite this whole logic step by step if (charges != null) { @@ -106,56 +146,50 @@ public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursement } for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { - currentInstallment.resetDerivedComponents(); + currentInstallment.resetBalances(); currentInstallment.updateDerivedFields(currency, disbursementDate); } - // TODO: Remove this reprocess and add the charges to the installment in chronological order - final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(currency, disbursementDate, installments, charges); + List chargeOrTransactions = createSortedChargesAndTransactionsList(loanTransactions, charges); + final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail(); - for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { - if (loanTransaction.getId() == null) { - processLatestTransaction(loanTransaction, currency, installments, charges, null); - loanTransaction.adjustInterestComponent(currency); - } else { - /** - * For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties) has - * changed.
- **/ - final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); - - // Reset derived component of new loan transaction and - // re-process transaction - processLatestTransaction(newLoanTransaction, currency, installments, charges, null); - newLoanTransaction.adjustInterestComponent(currency); - /** - * Check if the transaction amounts have changed. If so, reverse the original transaction and update - * changedTransactionDetail accordingly - **/ - if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) { - loanTransaction.updateLoanTransactionToRepaymentScheduleMappings( - newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings()); - } else { - createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail); - } - } + for (final ChargeOrTransaction chargeOrTransaction : chargeOrTransactions) { + chargeOrTransaction.getLoanTransaction().ifPresent(loanTransaction -> processSingleTransaction(loanTransaction, currency, + installments, charges, changedTransactionDetail)); + chargeOrTransaction.getLoanCharge() + .ifPresent(loanCharge -> processSingleCharge(loanCharge, currency, installments, disbursementDate)); } reprocessInstallments(installments, currency); return changedTransactionDetail; } + @NotNull + private List createSortedChargesAndTransactionsList(List loanTransactions, + Set charges) { + List chargeOrTransactions = new ArrayList<>(); + if (charges != null) { + chargeOrTransactions.addAll(charges.stream().map(ChargeOrTransaction::new).toList()); + } + if (loanTransactions != null) { + chargeOrTransactions.addAll(loanTransactions.stream().map(ChargeOrTransaction::new).toList()); + } + Collections.sort(chargeOrTransactions); + return chargeOrTransactions; + } + @Override public void processLatestTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List installments, Set charges, Money overpaidAmount) { switch (loanTransaction.getTypeOf()) { + case DISBURSEMENT -> handleDisbursement(loanTransaction, currency, installments); case WRITEOFF -> handleWriteOff(loanTransaction, currency, installments); case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, currency, installments, charges); case CHARGEBACK -> handleChargeback(loanTransaction, currency, overpaidAmount, installments); - case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT, WAIVE_INTEREST, RECOVERY_REPAYMENT -> handleRepayment(loanTransaction, currency, installments, charges); + case CHARGE_OFF -> handleChargeOff(loanTransaction, currency, installments); + case CHARGE_PAYMENT -> handleChargePayment(loanTransaction, currency, installments, charges); // TODO: Cover rest of the transaction types default -> { log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf()); @@ -163,6 +197,48 @@ public void processLatestTransaction(LoanTransaction loanTransaction, MonetaryCu } } + private void handleDisbursement(LoanTransaction loanTransaction, MonetaryCurrency currency, + List installments) { + updateLoanSchedule(loanTransaction, currency, installments); + } + + private void updateLoanSchedule(LoanTransaction disbursementTransaction, MonetaryCurrency currency, + List installments) { + final MathContext mc = MoneyHelper.getMathContext(); + List candidateRepaymentInstallments = installments.stream() + .filter(i -> !i.getDueDate().isBefore(disbursementTransaction.getTransactionDate()) && !i.isDownPayment()).toList(); + int noCandidateRepaymentInstallments = candidateRepaymentInstallments.size(); + LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail(); + Integer installmentAmountInMultiplesOf = disbursementTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf(); + Money downPaymentAmount = Money.zero(currency); + if (loanProductRelatedDetail.isEnableDownPayment()) { + LoanRepaymentScheduleInstallment downPaymentInstallment = installments.stream() + .filter(i -> i.isDownPayment() && i.getPrincipal(currency).isZero()).findFirst().orElseThrow(); + BigDecimal downPaymentAmt = MathUtil.percentageOf(disbursementTransaction.getAmount(), + loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment(), mc); + if (installmentAmountInMultiplesOf != null) { + downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf); + } + downPaymentAmount = Money.of(currency, downPaymentAmt); + downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount); + + } + Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount); + Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, mc.getRoundingMode()); + if (installmentAmountInMultiplesOf != null) { + increasePrincipalBy = Money.roundToMultiplesOf(increasePrincipalBy, installmentAmountInMultiplesOf); + } + Money remainingAmount = increasePrincipalBy.multiplyRetainScale(noCandidateRepaymentInstallments, mc.getRoundingMode()) + .minus(amortizableAmount); + + Money finalIncreasePrincipalBy = increasePrincipalBy; + candidateRepaymentInstallments + .forEach(i -> i.addToPrincipal(disbursementTransaction.getTransactionDate(), finalIncreasePrincipalBy)); + // Hence the rounding, we might need to amend the last installment amount + candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1) + .addToPrincipal(disbursementTransaction.getTransactionDate(), remainingAmount); + } + @Override public Money handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, List installments, Set loanCharges) { @@ -175,6 +251,153 @@ private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency c loanTransaction.resetDerivedComponents(); } Money transactionAmountUnprocessed = loanTransaction.getAmount(currency); + processTransaction(loanTransaction, currency, installments, transactionAmountUnprocessed, charges); + } + + private LoanTransactionToRepaymentScheduleMapping getTransactionMapping( + List transactionMappings, LoanTransaction loanTransaction, + LoanRepaymentScheduleInstallment currentInstallment, MonetaryCurrency currency) { + Money zero = Money.zero(currency); + LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = transactionMappings.stream() + .filter(e -> loanTransaction.equals(e.getLoanTransaction())) + .filter(e -> currentInstallment.equals(e.getLoanRepaymentScheduleInstallment())).findFirst().orElse(null); + if (loanTransactionToRepaymentScheduleMapping == null) { + loanTransactionToRepaymentScheduleMapping = LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, + currentInstallment, zero, zero, zero, zero); + transactionMappings.add(loanTransactionToRepaymentScheduleMapping); + } + return loanTransactionToRepaymentScheduleMapping; + } + + private Money payAllocation(PaymentAllocationType paymentAllocationType, LoanRepaymentScheduleInstallment currentInstallment, + LoanTransaction loanTransaction, Money transactionAmountUnprocessed, + LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Balances balances) { + LocalDate transactionDate = loanTransaction.getTransactionDate(); + Money zero = transactionAmountUnprocessed.zero(); + return switch (paymentAllocationType.getAllocationType()) { + case PENALTY -> { + Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountUnprocessed); + balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(subPenaltyPortion)); + addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, subPenaltyPortion); + yield subPenaltyPortion; + } + case FEE -> { + Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountUnprocessed); + balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(subFeePortion)); + addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, subFeePortion, zero); + yield subFeePortion; + } + case INTEREST -> { + Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountUnprocessed); + balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(subInterestPortion)); + addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, subInterestPortion, zero, zero); + yield subInterestPortion; + } + case PRINCIPAL -> { + Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountUnprocessed); + balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(subPrincipalPortion)); + addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, subPrincipalPortion, zero, zero, zero); + yield subPrincipalPortion; + } + }; + } + + private void addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, + Money principalPortion, Money interestPortion, Money feePortion, Money penaltyPortion) { + BigDecimal aggregatedPenalty = ObjectUtils + .defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPenaltyChargesPortion(), BigDecimal.ZERO) + .add(penaltyPortion.getAmount()); + BigDecimal aggregatedFee = ObjectUtils + .defaultIfNull(loanTransactionToRepaymentScheduleMapping.getFeeChargesPortion(), BigDecimal.ZERO) + .add(feePortion.getAmount()); + BigDecimal aggregatedInterest = ObjectUtils + .defaultIfNull(loanTransactionToRepaymentScheduleMapping.getInterestPortion(), BigDecimal.ZERO) + .add(interestPortion.getAmount()); + BigDecimal aggregatedPrincipal = ObjectUtils + .defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPrincipalPortion(), BigDecimal.ZERO) + .add(principalPortion.getAmount()); + loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal, aggregatedInterest, aggregatedFee, aggregatedPenalty); + } + + private void handleOverpayment(Money overpaymentPortion, LoanTransaction loanTransaction) { + if (overpaymentPortion.isGreaterThanZero()) { + onLoanOverpayment(loanTransaction, overpaymentPortion); + loanTransaction.updateOverPayments(overpaymentPortion); + } + } + + private void handleChargeOff(LoanTransaction loanTransaction, MonetaryCurrency currency, + List installments) { + loanTransaction.resetDerivedComponents(); + // determine how much is outstanding total and breakdown for principal, interest and charges + Money principalPortion = Money.zero(currency); + Money interestPortion = Money.zero(currency); + Money feeChargesPortion = Money.zero(currency); + Money penaltychargesPortion = Money.zero(currency); + for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { + if (currentInstallment.isNotFullyPaidOff()) { + principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency)); + interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(currency)); + feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency)); + penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency)); + } + } + + loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion); + } + + private void handleChargePayment(LoanTransaction loanTransaction, MonetaryCurrency currency, + List installments, Set charges) { + Money zero = Money.zero(currency); + Money feeChargesPortion = zero; + Money penaltyChargesPortion = zero; + List transactionMappings = new ArrayList<>(); + LoanChargePaidBy loanChargePaidBy = loanTransaction.getLoanChargesPaid().stream().findFirst().get(); + LoanCharge loanCharge = loanChargePaidBy.getLoanCharge(); + Money amountToBePaid = Money.of(currency, loanTransaction.getAmount()); + if (loanCharge.getAmountOutstanding(currency).isLessThan(amountToBePaid)) { + amountToBePaid = loanCharge.getAmountOutstanding(currency); + } + + LocalDate startDate = loanTransaction.getLoan().getDisbursementDate(); + + Money unprocessed = loanTransaction.getAmount(currency); + for (final LoanRepaymentScheduleInstallment installment : installments) { + boolean isDue = installment.isFirstPeriod() + ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate()) + : loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate()); + if (isDue) { + Integer installmentNumber = installment.getInstallmentNumber(); + Money paidAmount = loanCharge.updatePaidAmountBy(amountToBePaid, installmentNumber, zero); + + LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping( + transactionMappings, loanTransaction, installment, currency); + + if (loanTransaction.isPenaltyPayment()) { + penaltyChargesPortion = installment.payPenaltyChargesComponent(loanTransaction.getTransactionDate(), paidAmount); + loanTransaction.setLoanChargesPaid(Collections + .singleton(new LoanChargePaidBy(loanTransaction, loanCharge, paidAmount.getAmount(), installmentNumber))); + addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, penaltyChargesPortion); + } else { + feeChargesPortion = installment.payFeeChargesComponent(loanTransaction.getTransactionDate(), paidAmount); + loanTransaction.setLoanChargesPaid(Collections + .singleton(new LoanChargePaidBy(loanTransaction, loanCharge, paidAmount.getAmount(), installmentNumber))); + addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, feeChargesPortion, zero); + } + + loanTransaction.updateComponents(zero, zero, feeChargesPortion, penaltyChargesPortion); + unprocessed = loanTransaction.getAmount(currency).minus(paidAmount); + loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings); + } + } + + if (unprocessed.isGreaterThanZero()) { + processTransaction(loanTransaction, currency, installments, unprocessed, charges); + } + } + + private void processTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, + List installments, Money transactionAmountUnprocessed, Set charges) { Money zero = Money.zero(currency); List transactionMappings = new ArrayList<>(); @@ -193,7 +416,7 @@ private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency c switch (paymentAllocationType.getDueType()) { case PAST_DUE -> { currentInstallment = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff) - .filter(e -> loanTransaction.getTransactionDate().isAfter(e.getDueDate())) + .filter(e -> loanTransaction.isAfter(e.getDueDate())) .min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null); if (currentInstallment != null) { LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping( @@ -205,7 +428,7 @@ private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency c } case DUE -> { currentInstallment = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff) - .filter(e -> loanTransaction.getTransactionDate().isEqual(e.getDueDate())) + .filter(e -> loanTransaction.isOn(e.getDueDate())) .min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null); if (currentInstallment != null) { LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping( @@ -221,14 +444,14 @@ private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency c List currentInstallments = new ArrayList<>(); if (FutureInstallmentAllocationRule.REAMORTIZATION.equals(futureInstallmentAllocationRule)) { currentInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff) - .filter(e -> loanTransaction.getTransactionDate().isBefore(e.getDueDate())).toList(); + .filter(e -> loanTransaction.isBefore(e.getDueDate())).toList(); } else if (FutureInstallmentAllocationRule.NEXT_INSTALLMENT.equals(futureInstallmentAllocationRule)) { currentInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff) - .filter(e -> loanTransaction.getTransactionDate().isBefore(e.getDueDate())) + .filter(e -> loanTransaction.isBefore(e.getDueDate())) .min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList(); } else if (FutureInstallmentAllocationRule.LAST_INSTALLMENT.equals(futureInstallmentAllocationRule)) { currentInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff) - .filter(e -> loanTransaction.getTransactionDate().isBefore(e.getDueDate())) + .filter(e -> loanTransaction.isBefore(e.getDueDate())) .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList(); } int numberOfInstallments = currentInstallments.size(); @@ -271,96 +494,46 @@ private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency c balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion()); loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings); + payAdditionalCharges(loanTransaction, currency, charges); + + handleOverpayment(transactionAmountUnprocessed, loanTransaction); + } + + private void payAdditionalCharges(LoanTransaction loanTransaction, MonetaryCurrency currency, Set charges) { + final Set paidFeeCharges = loanTransaction.getLoanChargesPaid().stream() // + .map(LoanChargePaidBy::getLoanCharge) // + .filter(LoanCharge::isFeeCharge).collect(Collectors.toSet()); + final Set paidPenaltyCharges = loanTransaction.getLoanChargesPaid().stream() // + .map(LoanChargePaidBy::getLoanCharge) // + .filter(LoanCharge::isPenaltyCharge).collect(Collectors.toSet()); // TODO: rewrite to provide sorted list final Set loanFees = extractFeeCharges(charges); final Set loanPenalties = extractPenaltyCharges(charges); + loanFees.removeAll(paidFeeCharges); + loanPenalties.removeAll(paidPenaltyCharges); + + BigDecimal sumFeePaidAmount = paidFeeCharges.stream() // + .map(paidCharge -> paidCharge.getAmountPaid(currency)) // + .map(Money::getAmount) // + .reduce(BigDecimal.ZERO, BigDecimal::add); + + BigDecimal sumPenaltyPaidAmount = paidPenaltyCharges.stream() // + .map(paidCharge -> paidCharge.getAmountPaid(currency)) // + .map(Money::getAmount) // + .reduce(BigDecimal.ZERO, BigDecimal::add); if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual()) { - Money feeCharges = loanTransaction.getFeeChargesPortion(currency); - Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency); + Money feeCharges = loanTransaction.getFeeChargesPortion(currency).minus(sumFeePaidAmount); + Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency).minus(sumPenaltyPaidAmount); + if (feeCharges.isGreaterThanZero()) { - // TODO: Rewrite to exclude charge payment updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, null); } if (penaltyCharges.isGreaterThanZero()) { - // TODO: Rewrite to exclude charge payment updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, null); } } - handleOverpayment(transactionAmountUnprocessed, loanTransaction); - } - - private LoanTransactionToRepaymentScheduleMapping getTransactionMapping( - List transactionMappings, LoanTransaction loanTransaction, - LoanRepaymentScheduleInstallment currentInstallment, MonetaryCurrency currency) { - Money zero = Money.zero(currency); - LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = transactionMappings.stream() - .filter(e -> loanTransaction.equals(e.getLoanTransaction())) - .filter(e -> currentInstallment.equals(e.getLoanRepaymentScheduleInstallment())).findFirst().orElse(null); - if (loanTransactionToRepaymentScheduleMapping == null) { - loanTransactionToRepaymentScheduleMapping = LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, - currentInstallment, zero, zero, zero, zero); - transactionMappings.add(loanTransactionToRepaymentScheduleMapping); - } - return loanTransactionToRepaymentScheduleMapping; - } - - private Money payAllocation(PaymentAllocationType paymentAllocationType, LoanRepaymentScheduleInstallment currentInstallment, - LoanTransaction loanTransaction, Money transactionAmountUnprocessed, - LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Balances balances) { - LocalDate transactionDate = loanTransaction.getTransactionDate(); - Money zero = transactionAmountUnprocessed.zero(); - return switch (paymentAllocationType.getAllocationType()) { - case PENALTY -> { - Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountUnprocessed); - balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(subPenaltyPortion)); - addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, subPenaltyPortion); - yield subPenaltyPortion; - } - case FEE -> { - Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountUnprocessed); - balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(subFeePortion)); - addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, subFeePortion, zero); - yield subFeePortion; - } - case INTEREST -> { - Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountUnprocessed); - balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(subInterestPortion)); - addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, subInterestPortion, zero, zero); - yield subInterestPortion; - } - case PRINCIPAL -> { - Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountUnprocessed); - balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(subPrincipalPortion)); - addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, subPrincipalPortion, zero, zero, zero); - yield subPrincipalPortion; - } - }; - } - - private void addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, - Money principalPortion, Money interestPortion, Money feePortion, Money penaltyPortion) { - BigDecimal aggregatedPenalty = ObjectUtils - .defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPenaltyChargesPortion(), BigDecimal.ZERO) - .add(penaltyPortion.getAmount()); - BigDecimal aggregatedFee = ObjectUtils - .defaultIfNull(loanTransactionToRepaymentScheduleMapping.getFeeChargesPortion(), BigDecimal.ZERO) - .add(feePortion.getAmount()); - BigDecimal aggregatedInterest = ObjectUtils - .defaultIfNull(loanTransactionToRepaymentScheduleMapping.getInterestPortion(), BigDecimal.ZERO) - .add(interestPortion.getAmount()); - BigDecimal aggregatedPrincipal = ObjectUtils - .defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPrincipalPortion(), BigDecimal.ZERO) - .add(principalPortion.getAmount()); - loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal, aggregatedInterest, aggregatedFee, aggregatedPenalty); - } - - private void handleOverpayment(Money overpaymentPortion, LoanTransaction loanTransaction) { - if (overpaymentPortion.isGreaterThanZero()) { - onLoanOverpayment(loanTransaction, overpaymentPortion); - loanTransaction.updateOverPayments(overpaymentPortion); - } } @AllArgsConstructor diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java new file mode 100644 index 00000000000..34f677e6996 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java @@ -0,0 +1,88 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl; + +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.util.Optional; +import lombok.Getter; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.jetbrains.annotations.NotNull; + +@Getter +public class ChargeOrTransaction implements Comparable { + + private final Optional loanCharge; + private final Optional loanTransaction; + + public ChargeOrTransaction(LoanCharge loanCharge) { + this.loanCharge = Optional.of(loanCharge); + this.loanTransaction = Optional.empty(); + } + + public ChargeOrTransaction(LoanTransaction loanTransaction) { + this.loanTransaction = Optional.of(loanTransaction); + this.loanCharge = Optional.empty(); + } + + private LocalDate getEffectiveDate() { + if (loanCharge.isPresent()) { + return loanCharge.get().getDueDate(); + } else if (loanTransaction.isPresent()) { + return loanTransaction.get().getTransactionDate(); + } else { + throw new RuntimeException("Either charge or transaction should be present"); + } + } + + private LocalDate getSubmittedOnDate() { + if (loanCharge.isPresent()) { + return loanCharge.get().getSubmittedOnDate(); + } else if (loanTransaction.isPresent()) { + return loanTransaction.get().getSubmittedOnDate(); + } else { + throw new RuntimeException("Either charge or transaction should be present"); + } + } + + private OffsetDateTime getCreatedDateTime() { + if (loanCharge.isPresent() && loanCharge.get().getCreatedDate().isPresent()) { + return loanCharge.get().getCreatedDate().get(); + } else if (loanTransaction.isPresent()) { + return loanTransaction.get().getCreatedDateTime(); + } else { + throw new RuntimeException("Either charge with createdDate or transaction created datetime should be present"); + } + } + + @Override + public int compareTo(@NotNull ChargeOrTransaction o) { + int datePortion = this.getEffectiveDate().compareTo(o.getEffectiveDate()); + if (datePortion == 0) { + int submittedDate = this.getSubmittedOnDate().compareTo(o.getSubmittedOnDate()); + if (submittedDate == 0) { + return this.getCreatedDateTime().compareTo(o.getCreatedDateTime()); + } + return submittedDate; + } + return datePortion; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java index 10f37270859..2915ef9e4a4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java @@ -21,6 +21,7 @@ import java.time.LocalDate; import java.util.List; import java.util.Set; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; @@ -102,7 +103,7 @@ protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanR List orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isChargePending) .filter(loanCharge -> loanCharge.getEffectiveDueDate() == null - || !loanCharge.getEffectiveDueDate().isAfter(transactionDate)) + || !DateUtils.isAfter(loanCharge.getEffectiveDueDate(), transactionDate)) .sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList(); Money calculatedPenaltyCharge = Money.zero(currency); Money calculatedFeeCharge = Money.zero(currency); @@ -141,7 +142,7 @@ protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanR feeChargesPortion = feeChargesPortion.add(subFeePortion); Money subInterestPortion; - if (ignoreDueDateCheck || !transactionDate.isBefore(currentInstallment.getDueDate())) { + if (ignoreDueDateCheck || !DateUtils.isBefore(transactionDate, currentInstallment.getDueDate())) { subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion); interestPortion = interestPortion.add(subInterestPortion); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java index 804d62b8b41..09e8d649e02 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java @@ -21,6 +21,7 @@ import java.time.LocalDate; import java.util.List; import java.util.Set; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; @@ -102,7 +103,7 @@ protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanR List orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isChargePending) .filter(loanCharge -> loanCharge.getEffectiveDueDate() == null - || !loanCharge.getEffectiveDueDate().isAfter(transactionDate)) + || !DateUtils.isAfter(loanCharge.getEffectiveDueDate(), transactionDate)) .sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList(); Money calculatedPenaltyCharge = Money.zero(currency); Money calculatedFeeCharge = Money.zero(currency); @@ -129,13 +130,11 @@ protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanR penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion); Money subInterestPortion; - if (ignoreDueDateCheck || !transactionDate.isBefore(currentInstallment.getDueDate())) { + if (ignoreDueDateCheck || !DateUtils.isBefore(transactionDate, currentInstallment.getDueDate())) { subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion); interestPortion = interestPortion.add(subInterestPortion); - } - if (ignoreDueDateCheck || !transactionDate.isBefore(currentInstallment.getDueDate())) { Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion); principalPortion = principalPortion.add(subPrincipalPortion); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java index e94cee83a4f..3e86d3b1b43 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java @@ -21,6 +21,7 @@ import java.time.LocalDate; import java.util.List; import java.util.Set; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; @@ -85,9 +86,7 @@ protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallme final LoanRepaymentScheduleInstallment previousInstallment = installments.get(previousInstallmentIndex); lastInstallmentDueDate = previousInstallment.getDueDate(); - isInAdvance = !(transactionDate.isAfter(lastInstallmentDueDate) || transactionDate.isEqual(lastInstallmentDueDate)); - - return isInAdvance; + return DateUtils.isBefore(transactionDate, lastInstallmentDueDate); } /** diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java index 9987180b930..44642f5135e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java @@ -21,6 +21,7 @@ import java.time.LocalDate; import java.util.List; import java.util.Set; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; @@ -174,14 +175,12 @@ protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRep private LoanRepaymentScheduleInstallment nearestInstallment(final LocalDate transactionDate, final List installments) { - - LoanRepaymentScheduleInstallment nearest = installments.get(0); + LoanRepaymentScheduleInstallment nearest = installments.get(0); // installments must be sorted by dates for (final LoanRepaymentScheduleInstallment installment : installments) { - if (installment.getDueDate().isBefore(transactionDate) || installment.getDueDate().isEqual(transactionDate)) { - nearest = installment; - } else if (installment.getDueDate().isAfter(transactionDate)) { + if (DateUtils.isBefore(transactionDate, installment.getDueDate())) { break; } + nearest = installment; } return nearest; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java index 4b9f06465c4..0888fe95dee 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java @@ -327,7 +327,7 @@ private void holdGuarantorFunds(final Loan loan) { if (loan.isApproved() && !loan.isDisbursed()) { final List transactions = new ArrayList<>(); for (final SavingsAccountTransaction transaction : savingsAccount.getTransactions()) { - if (!transaction.getTransactionLocalDate().isAfter(loan.getApprovedOnDate())) { + if (!DateUtils.isAfter(transaction.getTransactionDate(), loan.getApprovedOnDate())) { transactions.add(transaction); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java index 770652be8b4..a62bc240675 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java @@ -187,12 +187,12 @@ private CommandProcessingResult createGuarantor(final Loan loan, final JsonComma private void validateGuarantorSavingsAccountActivationDateWithLoanSubmittedOnDate(final Loan loan, final SavingsAccount savingsAccount) { - if (loan.getSubmittedOnDate().isBefore(savingsAccount.getActivationLocalDate())) { + if (DateUtils.isBefore(loan.getSubmittedOnDate(), savingsAccount.getActivationDate())) { throw new GeneralPlatformDomainRuleException( "error.msg.guarantor.saving.account.activation.date.is.on.or.before.loan.submitted.on.date", - "Guarantor saving account activation date [" + savingsAccount.getActivationLocalDate() + "Guarantor saving account activation date [" + savingsAccount.getActivationDate() + "] is on or before the loan submitted on date [" + loan.getSubmittedOnDate() + "]", - savingsAccount.getActivationLocalDate(), loan.getSubmittedOnDate()); + savingsAccount.getActivationDate(), loan.getSubmittedOnDate()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java index ca9108eae79..cf77e23c62b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java @@ -27,6 +27,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanRescheduledDueHolidayBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.organisation.holiday.domain.Holiday; @@ -120,16 +121,15 @@ private void adjustRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adju // Loop through all loanRepayments List installments = loan.getRepaymentScheduleInstallments(); for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { - final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); // update from date if it's not same as previous installment's due // date. - if (!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)) { + if (!DateUtils.isEqual(tmpFromDate, loanRepaymentScheduleInstallment.getFromDate())) { loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); } - if (oldDueDate.equals(holiday.getFromDate()) || oldDueDate.isAfter(holiday.getFromDate())) { + if (!DateUtils.isBefore(oldDueDate, holiday.getFromDate())) { // FIXME: AA do we need to apply non-working days. // Assuming holiday's repayment reschedule to date cannot be // created on a non-working day. @@ -146,7 +146,7 @@ private LocalDate getNextRepaymentDate(Loan loan, Holiday holiday) { LocalDate adjustedRescheduleToDate = null; final LocalDate rescheduleToDate = holiday.getToDate(); for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) { - if (rescheduleToDate.isEqual(loanRepaymentScheduleInstallment.getDueDate())) { + if (DateUtils.isEqual(rescheduleToDate, loanRepaymentScheduleInstallment.getDueDate())) { adjustedRescheduleToDate = rescheduleToDate; break; } else { @@ -160,9 +160,9 @@ private LocalDate getNextRepaymentDate(Loan loan, Holiday holiday) { private LocalDate doStandardMonthlyCheck(LocalDate adjustedRescheduleToDate, LocalDate rescheduleToDate, LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) { // Standard Monthly Loan Holiday check - if (rescheduleToDate.isAfter(loanRepaymentScheduleInstallment.getDueDate()) - && rescheduleToDate.isBefore(loanRepaymentScheduleInstallment.getDueDate().plusDays(30))) { - adjustedRescheduleToDate = loanRepaymentScheduleInstallment.getDueDate(); + LocalDate dueDate = loanRepaymentScheduleInstallment.getDueDate(); + if (DateUtils.isAfter(rescheduleToDate, dueDate) && DateUtils.isBefore(rescheduleToDate, dueDate.plusDays(30))) { + adjustedRescheduleToDate = dueDate; } return adjustedRescheduleToDate; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansTasklet.java index 66c03a40136..a9bf92790d3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansTasklet.java @@ -64,7 +64,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon .retrieveInstallmentLoanCharges(chargeData.getId(), true); PortfolioAccountData portfolioAccountData = null; for (final LoanInstallmentChargeData installmentChargeData : chargePerInstallments) { - if (!installmentChargeData.getDueDate().isAfter(DateUtils.getBusinessLocalDate())) { + if (!DateUtils.isDateInTheFuture(installmentChargeData.getDueDate())) { if (portfolioAccountData == null) { portfolioAccountData = accountAssociationsReadPlatformService .retriveLoanLinkedAssociation(chargeData.getLoanId()); @@ -79,7 +79,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon transferFeeCharge(accountTransferDTO, errors); } } - } else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(DateUtils.getBusinessLocalDate())) { + } else if (chargeData.getDueDate() != null && !DateUtils.isDateInTheFuture(chargeData.getDueDate())) { final PortfolioAccountData portfolioAccountData = accountAssociationsReadPlatformService .retriveLoanLinkedAssociation(chargeData.getLoanId()); final boolean isExceptionForBalanceCheck = false; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java similarity index 82% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java rename to fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java index dc1a94e729c..236ecb7ed92 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java @@ -59,16 +59,12 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; -public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGenerator { - - protected final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); - private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator(); +public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator { @Override public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO) { - final LoanScheduleParams loanScheduleRecalculationDTO = null; - return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleRecalculationDTO); + return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, null); } private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, @@ -76,7 +72,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); // generate list of proposed schedule due dates - LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); + LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations() .fetchLoanTermDueDateVariationsData(loanEndDate); if (lastDueDateVariation != null) { @@ -91,8 +87,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // schedule generation. final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); - final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(); - LoanScheduleParams scheduleParams = null; + LoanScheduleParams scheduleParams; LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) ? loanApplicationTerms.getExpectedDisbursementDate() : loanApplicationTerms.getSubmittedOnDate(); @@ -110,17 +105,17 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = scheduleParams .getLoanRepaymentScheduleTransactionProcessor(); - Collection periods = new ArrayList<>(); + List periods = new ArrayList<>(); if (!scheduleParams.isPartialUpdate()) { - periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, loanApplicationTerms, - chargesDueAtTimeOfDisbursement, scheduleParams); + periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams, + chargesDueAtTimeOfDisbursement); } // Determine the total interest owed over the full loan for FLAT // interest method . if (!scheduleParams.isPartialUpdate() && !loanApplicationTerms.isEqualAmortization()) { Money totalInterestChargedForFullLoanTerm = loanApplicationTerms - .calculateTotalInterestCharged(this.paymentPeriodsInOneYearCalculator, mc); + .calculateTotalInterestCharged(getPaymentPeriodsInOneYearCalculator(), mc); loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm); @@ -130,31 +125,51 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe LocalDate lastRepaymentDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) ? loanApplicationTerms.getExpectedDisbursementDate() : loanApplicationTerms.getSubmittedOnDate(); - LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, + LocalDate firstRepaymentDate = getScheduledDateGenerator().generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment); - final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate( - loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate, + final LocalDate idealDisbursementDate = getScheduledDateGenerator().idealDisbursementDateBasedOnFirstRepaymentDate( + loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentDate, loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms); if (!scheduleParams.isPartialUpdate()) { - Long notDownPaymentPeriodNumber = periods.stream().filter(period -> !period.isDownPaymentPeriod()).count(); + BigDecimal downPaymentAmount = BigDecimal.ZERO; + if (loanApplicationTerms.isDownPaymentEnabled()) { + downPaymentAmount = MathUtil.percentageOf(scheduleParams.getOutstandingBalance().getAmount(), + loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, + loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } + + } + Money calculatedAmortizableAmount = loanApplicationTerms.getPrincipal().minus(downPaymentAmount); + scheduleParams.reduceOutstandingBalance(Money.of(currency, downPaymentAmount)); // Set Fixed Principal Amount - updateAmortization(mc, loanApplicationTerms, notDownPaymentPeriodNumber.intValue(), scheduleParams.getOutstandingBalance()); + updateAmortization(mc, loanApplicationTerms, scheduleParams.getPeriodNumber(), calculatedAmortizableAmount); if (loanApplicationTerms.isMultiDisburseLoan()) { /* fetches the first tranche amount and also updates other tranche details to map */ - BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(), periods, - chargesDueAtTimeOfDisbursement, scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation(), - scheduleParams); - scheduleParams.setPrincipalToBeScheduled(Money.of(currency, disburseAmt)); - loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt)); - scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, disburseAmt)); + BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(), + scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation()); + BigDecimal downPaymentAmt = BigDecimal.ZERO; + if (loanApplicationTerms.isDownPaymentEnabled()) { + downPaymentAmt = MathUtil.percentageOf(disburseAmt, loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), + 19); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } + } + BigDecimal remainingPrincipalAmt = disburseAmt.subtract(downPaymentAmt); + scheduleParams.setPrincipalToBeScheduled(Money.of(currency, remainingPrincipalAmt)); + loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(remainingPrincipalAmt)); + scheduleParams.setOutstandingBalance(Money.of(currency, remainingPrincipalAmt)); + scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, remainingPrincipalAmt)); } } // charges which depends on total loan interest will be added to this // set and handled separately after all installments generated - final Set nonCompoundingCharges = seperateTotalCompoundingPercentageCharges(loanCharges); + final Set nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges); LocalDate currentDate = DateUtils.getBusinessLocalDate(); LocalDate lastRestDate = currentDate; @@ -163,9 +178,9 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe } boolean isNextRepaymentAvailable = true; - Boolean extendTermForDailyRepayments = false; + boolean extendTermForDailyRepayments = false; - if (holidayDetailDTO.getWorkingDays().getExtendTermForDailyRepayments() == true + if (holidayDetailDTO.getWorkingDays().getExtendTermForDailyRepayments() && loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.DAYS && loanApplicationTerms.getRepaymentEvery() == 1) { holidayDetailDTO.getWorkingDays().setRepaymentReschedulingType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue()); @@ -184,16 +199,16 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe applyLoanVariationsForPartialScheduleGenerate(loanApplicationTerms, scheduleParams, interestRates, interestRatesForInstallments); - if (!firstRepaymentdate.isAfter(scheduleParams.getActualRepaymentDate())) { + if (!DateUtils.isAfter(firstRepaymentDate, scheduleParams.getActualRepaymentDate())) { isFirstRepayment = false; } } while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) { LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate(); - scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator + scheduleParams.setActualRepaymentDate(getScheduledDateGenerator() .generateNextRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment)); - AdjustedDateDetailsDTO adjustedDateDetailsDTO = this.scheduledDateGenerator + AdjustedDateDetailsDTO adjustedDateDetailsDTO = getScheduledDateGenerator() .adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO); scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate()); isFirstRepayment = false; @@ -201,17 +216,17 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // calculated interest start date for the period LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, - scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate, + scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentDate, loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(), loanApplicationTerms.getExpectedDisbursementDate()); // Loan Schedule Exceptions that need to be applied for Loan Account LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, - previousRepaymentDate, scheduledDueDate, interestRatesForInstallments, this.paymentPeriodsInOneYearCalculator, mc); + previousRepaymentDate, scheduledDueDate, interestRatesForInstallments, getPaymentPeriodsInOneYearCalculator(), mc); - scheduledDueDate = termVariationParams.getScheduledDueDate(); + scheduledDueDate = termVariationParams.scheduledDueDate(); if (!loanApplicationTerms.isFirstRepaymentDateAllowedOnHoliday()) { - AdjustedDateDetailsDTO adjustedDateDetailsDTO1 = this.scheduledDateGenerator.adjustRepaymentDate(scheduledDueDate, + AdjustedDateDetailsDTO adjustedDateDetailsDTO1 = getScheduledDateGenerator().adjustRepaymentDate(scheduledDueDate, loanApplicationTerms, holidayDetailDTO); scheduledDueDate = adjustedDateDetailsDTO1.getChangedScheduleDate(); } @@ -219,11 +234,11 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // Updates total days in term scheduleParams .addLoanTermInDays(Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), scheduledDueDate))); - if (termVariationParams.isSkipPeriod()) { + if (termVariationParams.skipPeriod()) { continue; } - if (scheduleParams.getPeriodStartDate().isAfter(scheduledDueDate)) { + if (DateUtils.isAfter(scheduleParams.getPeriodStartDate(), scheduledDueDate)) { throw new ScheduleDateException("Due date can't be before period start date", scheduledDueDate); } @@ -234,8 +249,9 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // this block is to generate the schedule till the specified // date(used for calculating preclosure) boolean isCompletePeriod = true; - if (scheduleParams.getScheduleTillDate() != null && !scheduledDueDate.isBefore(scheduleParams.getScheduleTillDate())) { - if (!scheduledDueDate.isEqual(scheduleParams.getScheduleTillDate())) { + if (scheduleParams.getScheduleTillDate() != null + && !DateUtils.isBefore(scheduledDueDate, scheduleParams.getScheduleTillDate())) { + if (!DateUtils.isEqual(scheduledDueDate, scheduleParams.getScheduleTillDate())) { isCompletePeriod = false; } scheduledDueDate = scheduleParams.getScheduleTillDate(); @@ -244,7 +260,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe if (loanApplicationTerms.isInterestRecalculationEnabled()) { populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, - holidayDetailDTO, scheduleParams, loanCharges, currency); + holidayDetailDTO, scheduleParams, loanCharges, currency, mc); } // populates the collection with transactions till the due date of @@ -252,16 +268,15 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe Collection applicableTransactions = getApplicableTransactionsForPeriod( scheduleParams.applyInterestRecalculation(), scheduledDueDate, transactions); - final double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator + final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction = getPaymentPeriodsInOneYearCalculator() .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate, loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), - loanApplicationTerms.getRepaymentEvery()); + loanApplicationTerms.getRepaymentEvery(), mc); ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, interestCalculationGraceOnRepaymentPeriodFraction); if (loanApplicationTerms.isMultiDisburseLoan()) { - updateBalanceBasedOnDisbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, - scheduledDueDate); + processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, scheduledDueDate); } // process repayments to the schedule as per the repayment @@ -270,32 +285,23 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // for a loan repayment which falls between the // two periods for interest first repayment strategies handleRecalculationForNonDueDateTransactions(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, scheduleParams, periods, - loanApplicationTerms.getTotalInterestDue(), idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate, + loanApplicationTerms.getTotalInterestDue(), idealDisbursementDate, firstRepaymentDate, lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams); if (currentPeriodParams.isSkipCurrentLoop()) { continue; } periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, - scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate, + scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentDate, loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(), loanApplicationTerms.getExpectedDisbursementDate()); // backup for pre-close transaction updateCompoundingDetails(scheduleParams, periodStartDateApplicableForInterest); - if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.isDownPaymentEnabled()) { - long numberOfDownPaymentPeriods = periods.stream() // - .filter(LoanScheduleModelPeriod::isDownPaymentPeriod).count(); - Long notApplicableForInstallmentRecalculationDownPaymentPeriods = numberOfDownPaymentPeriods - 1; - // Only the first down payment installment counts for the number of repayments - scheduleParams.setPeriodNumber( - scheduleParams.getPeriodNumber() - notApplicableForInstallmentRecalculationDownPaymentPeriods.intValue()); - } - // 5 determine principal,interest of repayment period PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), + getPaymentPeriodsInOneYearCalculator(), currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()), scheduleParams.getTotalCumulativeInterest(), loanApplicationTerms.getTotalInterestDue(), scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), @@ -332,7 +338,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe } // applies charges for the period - applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams); + applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams, mc); // sum up real totalInstallmentDue from components final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod(); @@ -367,10 +373,6 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe periods.add(installment); - if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.isDownPaymentEnabled()) { - createDownPaymentInstallmentForOverlappingInstallments(loanApplicationTerms, scheduleParams, periods, scheduledDueDate); - } - // Updates principal paid map with efective date for reducing // the amount from outstanding balance(interest calculation) updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams, @@ -384,7 +386,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe scheduleParams.setPeriodStartDate(scheduledDueDate); scheduleParams.incrementInstalmentNumber(); scheduleParams.incrementPeriodNumber(); - if (termVariationParams.isRecalculateAmounts()) { + if (termVariationParams.recalculateAmounts()) { loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null); loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null); adjustInstallmentOrPrincipalAmount(loanApplicationTerms, scheduleParams.getTotalCumulativePrincipal(), @@ -395,7 +397,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // this condition is to add the interest from grace period if not // already applied. if (scheduleParams.getTotalOutstandingInterestPaymentDueToGrace().isGreaterThanZero()) { - LoanScheduleModelPeriod installment = ((List) periods).get(periods.size() - 1); + LoanScheduleModelPeriod installment = periods.get(periods.size() - 1); installment.addInterestAmount(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); scheduleParams.addTotalRepaymentExpected(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); scheduleParams.addTotalCumulativeInterest(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); @@ -404,7 +406,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // determine fees and penalties for charges which depends on total // loan interest - updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges); + updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc); // this block is to add extra re-payment schedules with interest portion // if the loan not paid with in loan term @@ -413,8 +415,8 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe currentDate = scheduleParams.getScheduleTillDate(); } if (scheduleParams.applyInterestRecalculation() && scheduleParams.getLatePaymentMap().size() > 0 - && currentDate.isAfter(scheduleParams.getPeriodStartDate())) { - Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentdate(mc, loanApplicationTerms, holidayDetailDTO, currency, + && DateUtils.isAfter(currentDate, scheduleParams.getPeriodStartDate())) { + Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentDate(mc, loanApplicationTerms, holidayDetailDTO, currency, periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges, scheduleParams); scheduleParams.addTotalCumulativeInterest(totalInterest); } @@ -431,29 +433,6 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe totalOutstanding); } - private void createDownPaymentInstallmentForOverlappingInstallments(LoanApplicationTerms loanApplicationTerms, - LoanScheduleParams scheduleParams, Collection periods, LocalDate scheduledDueDate) { - for (Map.Entry disburseDetail : scheduleParams.getDisburseDetailMap().entrySet()) { - if (disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()) && disburseDetail.getKey().isEqual(scheduledDueDate)) { - scheduleParams.incrementPeriodNumber(); - scheduleParams.incrementInstalmentNumber(); - - BigDecimal downPaymentAmount = MathUtil.percentageOf(disburseDetail.getValue().getAmount(), - loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19); - Money downPayment = Money.of(loanApplicationTerms.getCurrency(), downPaymentAmount); - LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod.downPayment( - scheduleParams.getInstalmentNumber(), disburseDetail.getKey(), downPayment, scheduleParams.getOutstandingBalance()); - - addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); - periods.add(installment); - scheduleParams.reduceOutstandingBalance(downPayment); - scheduleParams.addTotalCumulativePrincipal(downPayment); - scheduleParams.addTotalRepaymentExpected(downPayment); - - } - } - } - private void updateCompoundingDetails(final Collection periods, final LoanScheduleParams params, final LoanApplicationTerms loanApplicationTerms) { final Map> compoundingDetails = params.getCompoundingDateVariations(); @@ -465,7 +444,8 @@ private void updateCompoundingDetails(final Collection Map periodCompoundingDetails = compoundingDetails.get(loanScheduleModelPeriod.periodFromDate()); if (periodCompoundingDetails != null) { for (Map.Entry entry : periodCompoundingDetails.entrySet()) { - if (entry.getValue().isGreaterThanZero() && !entry.getKey().isAfter(loanScheduleModelPeriod.periodDueDate())) { + if (entry.getValue().isGreaterThanZero() + && !DateUtils.isAfter(entry.getKey(), loanScheduleModelPeriod.periodDueDate())) { LocalDate effectiveDate = entry.getKey(); if (loanApplicationTerms.allowCompoundingOnEod()) { effectiveDate = effectiveDate.minusDays(1); @@ -481,21 +461,22 @@ private void updateCompoundingDetails(final Collection } private void applyChargesForCurrentPeriod(final Set loanCharges, final MonetaryCurrency currency, - LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams) { + LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams, + final MathContext mc) { PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(), currentPeriodParams.getInterestForThisPeriod(), null); currentPeriodParams.setFeeChargesForInstallment(cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), - scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod())); + scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); currentPeriodParams.setPenaltyChargesForInstallment(cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), - scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod())); + scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); scheduleParams.addTotalFeeChargesCharged(currentPeriodParams.getFeeChargesForInstallment()); scheduleParams.addTotalPenaltyChargesCharged(currentPeriodParams.getPenaltyChargesForInstallment()); } private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams, - final Collection periods, final Set nonCompoundingCharges) { + final Collection periods, final Set nonCompoundingCharges, final MathContext mc) { for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { if (loanScheduleModelPeriod.isRepaymentPeriod()) { PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()), @@ -503,11 +484,11 @@ private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanSched Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), - !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod()); + !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), - !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod()); + !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc); scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment); scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment); scheduleParams.addTotalRepaymentExpected(feeChargesForInstallment.plus(penaltyChargesForInstallment)); @@ -562,17 +543,17 @@ private LoanScheduleModelPeriod handleRecalculationForTransactions(final MathCon currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), currentPeriodParams, lastTotalOutstandingInterestPaymentDueToGrace, transactionDate, modifiedInstallment, loanCharges); - Money addToPrinciapal = Money.zero(currency); + Money addToPrincipal = Money.zero(currency); if (scheduleParams.getOutstandingBalance().isLessThanZero()) { - addToPrinciapal = addToPrinciapal.plus(scheduleParams.getOutstandingBalance()); + addToPrincipal = addToPrincipal.plus(scheduleParams.getOutstandingBalance()); scheduleParams.setOutstandingBalance(Money.zero(currency)); } updateAmountsBasedOnEarlyPayment(loanApplicationTerms, holidayDetailDTO, scheduleParams, modifiedInstallment, - detail, unprocessed, addToPrinciapal); + detail, unprocessed, addToPrincipal); scheduleParams.addReducePrincipal(unprocessed); - currentPeriodParams.plusPrincipalForThisPeriod(unprocessed.plus(addToPrinciapal)); - principalProcessed = principalProcessed.plus(unprocessed.plus(addToPrinciapal)); + currentPeriodParams.plusPrincipalForThisPeriod(unprocessed.plus(addToPrincipal)); + principalProcessed = principalProcessed.plus(unprocessed.plus(addToPrincipal)); BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount(); scheduleParams .setReducePrincipal(applyEarlyPaymentStrategy(loanApplicationTerms, scheduleParams.getReducePrincipal(), @@ -599,21 +580,21 @@ private LoanScheduleModelPeriod handleRecalculationForTransactions(final MathCon private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams scheduleParams, final Money totalInterestChargedForFullLoanTerm, final LocalDate scheduledDueDate, - LocalDate periodStartDateApplicableForInterest, final double interestCalculationGraceOnRepaymentPeriodFraction, + LocalDate periodStartDateApplicableForInterest, final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final ScheduleCurrentPeriodParams currentPeriodParams, final Money lastTotalOutstandingInterestPaymentDueToGrace, final LocalDate transactionDate, final LoanScheduleModelPeriod installment, Set loanCharges) { LoanScheduleModelPeriod modifiedInstallment = installment; - Money oustanding = scheduleParams.getOutstandingBalance(); + Money outstanding = scheduleParams.getOutstandingBalance(); PrincipalInterest tempPrincipalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(), currentPeriodParams.getInterestForThisPeriod(), null); - oustanding = oustanding.minus(cumulativeFeeChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, + outstanding = outstanding.minus(cumulativeFeeChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, totalInterestChargedForFullLoanTerm.getCurrency(), tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), - scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod())); - oustanding = oustanding.minus(cumulativePenaltyChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, + scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); + outstanding = outstanding.minus(cumulativePenaltyChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, totalInterestChargedForFullLoanTerm.getCurrency(), tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), - scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod())); + scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); - if (!oustanding.isGreaterThan(currentPeriodParams.getInterestForThisPeriod()) && !scheduledDueDate.equals(transactionDate)) { + if (!outstanding.isGreaterThan(currentPeriodParams.getInterestForThisPeriod()) && !scheduledDueDate.equals(transactionDate)) { final Collection interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); LocalDate calculateTill = transactionDate; if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) { @@ -633,7 +614,7 @@ private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, fin new TreeMap<>(scheduleParams.getCompoundingMap())); scheduleParams.getCompoundingMap().clear(); populateCompoundingDatesInPeriod(periodStartDateApplicableForInterest, calculateTill, loanApplicationTerms, holidayDetailDTO, - scheduleParams, loanCharges, totalInterestChargedForFullLoanTerm.getCurrency()); + scheduleParams, loanCharges, totalInterestChargedForFullLoanTerm.getCurrency(), mc); // this is to make sure we are recalculating using correct interest // rate @@ -645,7 +626,7 @@ private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, fin } } - PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod(this.paymentPeriodsInOneYearCalculator, + PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod(getPaymentPeriodsInOneYearCalculator(), interestCalculationGraceOnRepaymentPeriodFraction, scheduleParams.getTotalCumulativePrincipal(), scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, lastTotalOutstandingInterestPaymentDueToGrace, scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, @@ -658,7 +639,7 @@ private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, fin totalInterestChargedForFullLoanTerm.getCurrency(), interestCalculationGraceOnRepaymentPeriodFraction); tempPeriod.setInterestForThisPeriod(interestTillDate.interest()); applyChargesForCurrentPeriod(loanCharges, totalInterestChargedForFullLoanTerm.getCurrency(), scheduleParams, calculateTill, - tempPeriod); + tempPeriod, mc); Money interestDiff = currentPeriodParams.getInterestForThisPeriod().minus(tempPeriod.getInterestForThisPeriod()); Money chargeDiff = currentPeriodParams.getFeeChargesForInstallment().minus(tempPeriod.getFeeChargesForInstallment()); Money penaltyDiff = currentPeriodParams.getPenaltyChargesForInstallment().minus(tempPeriod.getPenaltyChargesForInstallment()); @@ -733,7 +714,7 @@ private void updateCompoundingDetails(LoanScheduleParams scheduleParams, LocalDa private void handleRecalculationForNonDueDateTransactions(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams, final Collection periods, final Money totalInterestChargedForFullLoanTerm, - final LocalDate idealDisbursementDate, LocalDate firstRepaymentdate, final LocalDate lastRestDate, + final LocalDate idealDisbursementDate, LocalDate firstRepaymentDate, final LocalDate lastRestDate, final LocalDate scheduledDueDate, final LocalDate periodStartDateForInterest, final Collection applicableTransactions, final ScheduleCurrentPeriodParams currentPeriodParams) { if (scheduleParams.applyInterestRecalculation()) { @@ -750,7 +731,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, } boolean updateLatePaymentMap = false; final LocalDate transactionDate = detail.getTransactionDate(); - if (transactionDate.isBefore(scheduledDueDate)) { + if (DateUtils.isBefore(transactionDate, scheduledDueDate)) { if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null && scheduleParams .getLoanRepaymentScheduleTransactionProcessor().isInterestFirstRepaymentScheduleTransactionProcessor()) { if (detail.getTransaction().isWaiver()) { @@ -764,24 +745,23 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, processTransactions.clear(); currentTransactions.addAll(createCurrentTransactionList(detail)); - if (!transactionDate.isEqual(scheduleParams.getPeriodStartDate()) || scheduleParams.isFirstPeriod()) { - + if (!DateUtils.isEqual(transactionDate, scheduleParams.getPeriodStartDate()) || scheduleParams.isFirstPeriod()) { int periodDays = Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), transactionDate)); // calculates period start date for interest // calculation as per the configuration periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, - scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate, + scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentDate, loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(), loanApplicationTerms.getExpectedDisbursementDate()); int daysInPeriodApplicable = Math .toIntExact(ChronoUnit.DAYS.between(periodStartDateApplicableForInterest, transactionDate)); - Money interestForThisinstallment = Money.zero(currency); + Money interestForCurrentInstallment = Money.zero(currency); if (daysInPeriodApplicable > 0) { // 5 determine interest till the transaction // date PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, + getPaymentPeriodsInOneYearCalculator(), currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()), scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, @@ -790,7 +770,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, transactionDate, interestRates); - interestForThisinstallment = principalInterestForThisPeriod.interest(); + interestForCurrentInstallment = principalInterestForThisPeriod.interest(); scheduleParams.setTotalOutstandingInterestPaymentDueToGrace( principalInterestForThisPeriod.interestPaymentDueToGrace()); @@ -801,23 +781,23 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, // applies all the applicable charges to the // newly // created installment - PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod, interestForThisinstallment, - null); + PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod, + interestForCurrentInstallment, null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(), transactionDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), - scheduleParams.getTotalCumulativeInterest(), false, scheduleParams.isFirstPeriod()); + scheduleParams.getTotalCumulativeInterest(), false, scheduleParams.isFirstPeriod(), mc); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(), transactionDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), - scheduleParams.getTotalCumulativeInterest(), false, scheduleParams.isFirstPeriod()); + scheduleParams.getTotalCumulativeInterest(), false, scheduleParams.isFirstPeriod(), mc); // sum up real totalInstallmentDue from // components - final Money totalInstallmentDue = principalForThisPeriod.plus(interestForThisinstallment) + final Money totalInstallmentDue = principalForThisPeriod.plus(interestForCurrentInstallment) .plus(feeChargesForInstallment).plus(penaltyChargesForInstallment); // create repayment period from parts installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(), scheduleParams.getPeriodStartDate(), transactionDate, principalForThisPeriod, - scheduleParams.getOutstandingBalance(), interestForThisinstallment, feeChargesForInstallment, + scheduleParams.getOutstandingBalance(), interestForCurrentInstallment, feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue, true); periods.add(installment); addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); @@ -830,7 +810,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, // handle cumulative fields scheduleParams.addLoanTermInDays(periodDays); scheduleParams.addTotalRepaymentExpected(totalInstallmentDue); - scheduleParams.addTotalCumulativeInterest(interestForThisinstallment); + scheduleParams.addTotalCumulativeInterest(interestForCurrentInstallment); scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment); scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment); @@ -839,7 +819,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, updateLatePaymentMap = true; scheduleParams.incrementInstalmentNumber(); populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, - holidayDetailDTO, scheduleParams, loanCharges, currency); + holidayDetailDTO, scheduleParams, loanCharges, currency, mc); // creates and insert Loan repayment schedule // for // the period @@ -858,16 +838,16 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) { LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); - checkForOutstanding = transactionDate.isEqual(applicableDate); + checkForOutstanding = DateUtils.isEqual(transactionDate, applicableDate); } // reduces actual outstanding balance scheduleParams.reduceOutstandingBalance(unprocessed); // if outstanding balance becomes less than zero // then adjusts the princiapal - Money addToPrinciapal = Money.zero(currency); + Money addToPrincipal = Money.zero(currency); if (!scheduleParams.getOutstandingBalance().isGreaterThanZero()) { - addToPrinciapal = addToPrinciapal.plus(scheduleParams.getOutstandingBalance()); + addToPrincipal = addToPrincipal.plus(scheduleParams.getOutstandingBalance()); scheduleParams.setOutstandingBalance(Money.zero(currency)); currentPeriodParams.setLastInstallment(installment); } @@ -875,7 +855,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, // payment amounts and applicable date as per // rest updateAmountsBasedOnEarlyPayment(loanApplicationTerms, holidayDetailDTO, scheduleParams, installment, detail, - unprocessed, addToPrinciapal); + unprocessed, addToPrincipal); // method applies early payment strategy scheduleParams.addReducePrincipal(unprocessed); @@ -894,7 +874,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, } else if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null) { LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); - if (applicableDate.isBefore(scheduledDueDate)) { + if (DateUtils.isBefore(applicableDate, scheduledDueDate)) { List currentTransactions = createCurrentTransactionList(detail); Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor() .handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments(), loanCharges); @@ -911,16 +891,15 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, new TreeMap<>(scheduleParams.getCompoundingMap())); - LocalDate calculateTill = transactionDate; PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, + getPaymentPeriodsInOneYearCalculator(), currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()), scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), - scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, calculateTill, + scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, transactionDate, interestRates); if (!principalInterestForThisPeriod.interest() .plus(principalInterestForThisPeriod.interestPaymentDueToGrace()) @@ -944,16 +923,16 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, // if outstanding balance becomes less than // zero // then adjusts the princiapal - Money addToPrinciapal = Money.zero(currency); + Money addToPrincipal = Money.zero(currency); if (scheduleParams.getOutstandingBalance().isLessThanZero()) { - addToPrinciapal = addToPrinciapal.plus(scheduleParams.getOutstandingBalance()); + addToPrincipal = addToPrincipal.plus(scheduleParams.getOutstandingBalance()); scheduleParams.setOutstandingBalance(Money.zero(currency)); - updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), addToPrinciapal, applicableDate); - currentPeriodParams.plusEarlyPaidAmount(addToPrinciapal); + updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), addToPrincipal, applicableDate); + currentPeriodParams.plusEarlyPaidAmount(addToPrincipal); } } - if (arrears.isGreaterThanZero() && applicableDate.isBefore(lastRestDate)) { + if (arrears.isGreaterThanZero() && DateUtils.isBefore(applicableDate, lastRestDate)) { handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail); } } @@ -985,11 +964,11 @@ private void handleLatePayments(final LoanApplicationTerms loanApplicationTerms, private void updateAmountsBasedOnEarlyPayment(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams, final LoanScheduleModelPeriod installment, RecalculationDetail detail, Money unprocessed, - Money addToPrinciapal) { + Money addToPrincipal) { updatePrincipalPaidPortionToMap(loanApplicationTerms, holidayDetailDTO, scheduleParams.getPrincipalPortionMap(), installment, - detail, unprocessed.plus(addToPrinciapal), scheduleParams.getInstallments()); - scheduleParams.addTotalRepaymentExpected(unprocessed.plus(addToPrinciapal)); - scheduleParams.addTotalCumulativePrincipal(unprocessed.plus(addToPrinciapal)); + detail, unprocessed.plus(addToPrincipal), scheduleParams.getInstallments()); + scheduleParams.addTotalRepaymentExpected(unprocessed.plus(addToPrincipal)); + scheduleParams.addTotalCumulativePrincipal(unprocessed.plus(addToPrincipal)); } private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) { @@ -1000,22 +979,21 @@ private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams schedule } /** - * Method updates outstanding balance of the loan for interest calculation + * Method add extra disbursement periods (if applicable) and update the schedule params * */ - private void updateBalanceBasedOnDisbursement(final LoanApplicationTerms loanApplicationTerms, - final BigDecimal chargesDueAtTimeOfDisbursement, LoanScheduleParams scheduleParams, - final Collection periods, final LocalDate scheduledDueDate) { + private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement, + LoanScheduleParams scheduleParams, final Collection periods, final LocalDate scheduledDueDate) { for (Map.Entry disburseDetail : scheduleParams.getDisburseDetailMap().entrySet()) { - if (disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()) - && !disburseDetail.getKey().isAfter(scheduledDueDate)) { + if (DateUtils.isAfter(disburseDetail.getKey(), scheduleParams.getPeriodStartDate()) + && !DateUtils.isAfter(disburseDetail.getKey(), scheduledDueDate)) { // validation check for amount not exceeds specified max // amount as per the configuration - if (loanApplicationTerms.getMaxOutstandingBalance() != null && scheduleParams.getOutstandingBalance() - .plus(disburseDetail.getValue()).isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) { - String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance(); - throw new MultiDisbursementOutstandingAmoutException(errorMsg, - loanApplicationTerms.getMaxOutstandingBalance().getAmount(), disburseDetail.getValue()); + Money maxOutstandingBalance = loanApplicationTerms.getMaxOutstandingBalance(); + if (scheduleParams.getOutstandingBalance().plus(disburseDetail.getValue()).isGreaterThan(maxOutstandingBalance)) { + String errorMsg = "Outstanding balance must not exceed the amount: " + maxOutstandingBalance; + throw new MultiDisbursementOutstandingAmoutException(errorMsg, maxOutstandingBalance.getAmount(), + disburseDetail.getValue()); } // creates and add disbursement detail to the repayments @@ -1024,18 +1002,19 @@ private void updateBalanceBasedOnDisbursement(final LoanApplicationTerms loanApp .disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement); periods.add(disbursementPeriod); + BigDecimal downPaymentAmt = BigDecimal.ZERO; if (loanApplicationTerms.isDownPaymentEnabled()) { - if (!disburseDetail.getKey().isEqual(scheduledDueDate)) { - createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, disburseDetail.getKey(), - disburseDetail.getValue().getAmount()); - } + final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms, + scheduleParams, disburseDetail.getKey(), disburseDetail.getValue().getAmount()); + periods.add(downPaymentPeriod); + downPaymentAmt = downPaymentPeriod.principalDue(); } - // updates actual outstanding balance with new // disbursement detail - scheduleParams.addOutstandingBalance(disburseDetail.getValue()); - scheduleParams.addPrincipalToBeScheduled(disburseDetail.getValue()); - loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseDetail.getValue())); + Money remainingPrincipal = disburseDetail.getValue().minus(downPaymentAmt); + scheduleParams.addOutstandingBalance(remainingPrincipal); + scheduleParams.addPrincipalToBeScheduled(remainingPrincipal); + loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(remainingPrincipal)); } } } @@ -1060,7 +1039,7 @@ private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTer // due date changes should be applied only for that dueDate if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) { LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); - if (loanTermVariationsData.getTermVariationApplicableFrom().isEqual(modifiedScheduledDueDate)) { + if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) { modifiedScheduledDueDate = loanTermVariationsData.getDateValue(); if (!loanTermVariationsData.isSpecificToInstallment()) { scheduleParams.setActualRepaymentDate(modifiedScheduledDueDate); @@ -1078,9 +1057,8 @@ private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTer loanApplicationTerms.setFixedEmiAmount(null); } } else { - Money totalInterestDueForLoan = Money.zero(loanApplicationTerms.getCurrency()); loanApplicationTerms.setTotalPrincipalAccountedForInterestCalculation(scheduleParams.getTotalCumulativePrincipal()); - totalInterestDueForLoan = loanApplicationTerms.calculateTotalInterestCharged(calculator, mc); + Money totalInterestDueForLoan = loanApplicationTerms.calculateTotalInterestCharged(calculator, mc); totalInterestDueForLoan = totalInterestDueForLoan.plus(scheduleParams.getTotalCumulativeInterest()); loanApplicationTerms.updateTotalInterestDue(totalInterestDueForLoan); // exclude till last period in calculations @@ -1112,7 +1090,7 @@ private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTer loanTermVariationsData.setProcessed(true); break; case DELETE_INSTALLMENT: - if (loanTermVariationsData.getTermVariationApplicableFrom().isEqual(modifiedScheduledDueDate)) { + if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) { skipPeriod = true; loanTermVariationsData.setProcessed(true); } @@ -1140,7 +1118,7 @@ private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTer Integer rescheduleNumberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); rescheduleNumberOfRepayments += loanTermVariationsData.getDecimalValue().intValue(); loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments); - LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, + LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, loanApplicationTerms.getHolidayDetailDTO()); loanApplicationTerms.updateLoanEndDate(loanEndDate); loanApplicationTerms.updateAccountedTillPeriod(scheduleParams.getPeriodNumber() - 1, @@ -1170,9 +1148,7 @@ private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTer } } - LoanTermVariationParams termVariationParams = new LoanTermVariationParams(skipPeriod, recalculateAmounts, modifiedScheduledDueDate, - variationsData); - return termVariationParams; + return new LoanTermVariationParams(skipPeriod, recalculateAmounts, modifiedScheduledDueDate, variationsData); } /** @@ -1220,7 +1196,7 @@ private LoanTermVariationParams applyExceptionLoanTermVariations(final LoanAppli variationsData.add(loanTermVariationsData); break; case DELETE_INSTALLMENT: - if (loanTermVariationsData.getTermVariationApplicableFrom().isEqual(modifiedScheduledDueDate)) { + if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) { skipPeriod = true; variationsData.add(loanTermVariationsData); } @@ -1245,7 +1221,7 @@ private LoanTermVariationParams applyExceptionLoanTermVariations(final LoanAppli rescheduleNumberOfRepayments += loanTermVariationsData.getDecimalValue().intValue(); loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments); // generate list of proposed schedule due dates - LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, + LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, loanApplicationTerms.getHolidayDetailDTO()); loanApplicationTerms.updateLoanEndDate(loanEndDate); adjustInstallmentOrPrincipalAmount(loanApplicationTerms, totalCumulativePrincipal, instalmentNumber, mc); @@ -1258,9 +1234,7 @@ private LoanTermVariationParams applyExceptionLoanTermVariations(final LoanAppli } } - LoanTermVariationParams termVariationParams = new LoanTermVariationParams(skipPeriod, recalculateAmounts, modifiedScheduledDueDate, - variationsData); - return termVariationParams; + return new LoanTermVariationParams(skipPeriod, recalculateAmounts, modifiedScheduledDueDate, variationsData); } /** @@ -1336,7 +1310,7 @@ private boolean updateFixedInstallmentAmount(final MathContext mc, final LoanApp if (periodNumber < loanApplicationTerms.getPrincipalGrace() + 1) { periodNumber = loanApplicationTerms.getPrincipalGrace() + 1; } - Money emiAmount = loanApplicationTerms.pmtForInstallment(this.paymentPeriodsInOneYearCalculator, outstandingBalance, + Money emiAmount = loanApplicationTerms.pmtForInstallment(getPaymentPeriodsInOneYearCalculator(), outstandingBalance, periodNumber, mc); loanApplicationTerms.setFixedEmiAmount(emiAmount.getAmount()); isAmountChanged = true; @@ -1369,7 +1343,7 @@ private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationT * current date and adds new repayment schedule detail * */ - private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + private Money addInterestOnlyRepaymentScheduleForCurrentDate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final Collection periods, final LocalDate currentDate, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final Collection transactions, final Set loanCharges, final LoanScheduleParams params) { @@ -1378,7 +1352,7 @@ private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext m Money outstanding = params.getOutstandingBalanceAsPerRest(); Money totalInterest = Money.zero(currency); Money totalCumulativeInterest = Money.zero(currency); - double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); + BigDecimal interestCalculationGraceOnRepaymentPeriodFraction = BigDecimal.ZERO; int periodNumberTemp = 1; LocalDate lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); Collection applicableVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); @@ -1386,10 +1360,9 @@ private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext m LocalDate additionalPeriodsStartDate = params.getPeriodStartDate(); do { - - params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(), + params.setActualRepaymentDate(getScheduledDateGenerator().generateNextRepaymentDate(params.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment)); - if (params.getActualRepaymentDate().isAfter(currentDate)) { + if (DateUtils.isAfter(params.getActualRepaymentDate(), currentDate)) { params.setActualRepaymentDate(currentDate); } @@ -1397,7 +1370,7 @@ private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext m params.getActualRepaymentDate(), transactions); populateCompoundingDatesInPeriod(params.getPeriodStartDate(), params.getActualRepaymentDate(), loanApplicationTerms, - holidayDetailDTO, params, loanCharges, currency); + holidayDetailDTO, params, loanCharges, currency, mc); for (RecalculationDetail detail : applicableTransactions) { if (detail.isProcessed()) { @@ -1406,9 +1379,9 @@ private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext m LocalDate transactionDate = detail.getTransactionDate(); List currentTransactions = createCurrentTransactionList(detail); - if (!params.getPeriodStartDate().isEqual(transactionDate)) { + if (!DateUtils.isEqual(transactionDate, params.getPeriodStartDate())) { PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), + getPaymentPeriodsInOneYearCalculator(), interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), params.getPeriodStartDate(), transactionDate, applicableVariations); @@ -1426,7 +1399,7 @@ periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), addLoanRepaymentScheduleInstallment(params.getInstallments(), installment); updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, params, lastRestDate, transactionDate); populateCompoundingDatesInPeriod(installment.periodDueDate(), params.getActualRepaymentDate(), loanApplicationTerms, - holidayDetailDTO, params, loanCharges, currency); + holidayDetailDTO, params, loanCharges, currency, mc); uncompoundedFromLastInstallment = params.getUnCompoundedAmount(); params.setPeriodStartDate(transactionDate); startDate = transactionDate; @@ -1451,7 +1424,7 @@ periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), if (!outstanding.isZero()) { PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), + getPaymentPeriodsInOneYearCalculator(), interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), params.getPeriodStartDate(), params.getActualRepaymentDate(), applicableVariations); @@ -1462,15 +1435,13 @@ periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), Money uncompounded = params.getUnCompoundedAmount(); Money compounded = uncompounded.zero(); for (Map.Entry mapEntry : params.getCompoundingMap().entrySet()) { - if (mapEntry.getKey().isAfter(params.getPeriodStartDate())) { + if (DateUtils.isAfter(mapEntry.getKey(), params.getPeriodStartDate())) { compounded = compounded.plus(mapEntry.getValue()); } } - if (compounded.isGreaterThanZero() && startDate.isEqual(additionalPeriodsStartDate)) { - params.setCompoundedInLastInstallment(uncompoundedFromLastInstallment);// uncompounded - // in - // last - // installment + if (compounded.isGreaterThanZero() && DateUtils.isEqual(startDate, additionalPeriodsStartDate)) { + // uncompounded in last installment + params.setCompoundedInLastInstallment(uncompoundedFromLastInstallment); additionalPeriodsStartDate = additionalPeriodsStartDate.plusDays(1); } Money compoundedForThisPeriod = compounded.minus(uncompounded); @@ -1481,14 +1452,14 @@ periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), compoundingDate = compoundingDate.minusDays(1); } compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO); - if (compoundingDate.isEqual(params.getActualRepaymentDate())) { + if (DateUtils.isEqual(compoundingDate, params.getActualRepaymentDate())) { params.getCompoundingMap().put(compoundingDate, uncompoundedForThisPeriod); params.setUnCompoundedAmount(uncompoundedForThisPeriod.zero()); } } } params.setPeriodStartDate(params.getActualRepaymentDate()); - } while (params.getActualRepaymentDate().isBefore(currentDate) && !outstanding.isZero()); + } while (DateUtils.isBefore(params.getActualRepaymentDate(), currentDate) && !outstanding.isZero()); if (totalInterest.isGreaterThanZero()) { LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(), @@ -1518,7 +1489,7 @@ private Collection getApplicableTransactionsForPeriod(final Collection applicableTransactions = new ArrayList<>(); if (applyInterestRecalculation && !Objects.isNull(transactions)) { for (RecalculationDetail detail : transactions) { - if (!detail.getTransactionDate().isAfter(repaymentDate)) { + if (!DateUtils.isBefore(repaymentDate, detail.getTransactionDate())) { applicableTransactions.add(detail); } } @@ -1586,19 +1557,19 @@ private void adjustInstallmentOrPrincipalAmount(final LoanApplicationTerms loanA */ private Money updateBalanceForInterestCalculation(final Map principalPortionMap, final LocalDate scheduledDueDate, final Money outstandingBalanceAsPerRest, boolean addMapDetails) { - List removeFromprincipalPortionMap = new ArrayList<>(); + List removeFromPrincipalPortionMap = new ArrayList<>(); Money outstandingBalance = outstandingBalanceAsPerRest; for (Map.Entry principal : principalPortionMap.entrySet()) { - if (!principal.getKey().isAfter(scheduledDueDate)) { + if (!DateUtils.isAfter(principal.getKey(), scheduledDueDate)) { if (addMapDetails) { outstandingBalance = outstandingBalance.plus(principal.getValue()); } else { outstandingBalance = outstandingBalance.minus(principal.getValue()); } - removeFromprincipalPortionMap.add(principal.getKey()); + removeFromPrincipalPortionMap.add(principal.getKey()); } } - for (LocalDate date : removeFromprincipalPortionMap) { + for (LocalDate date : removeFromPrincipalPortionMap) { principalPortionMap.remove(date); } return outstandingBalance; @@ -1625,11 +1596,11 @@ private void updateLatePaymentCompoundingAmount(final Map prin Money appliedOnPrincipalVariationMap = Money.zero(currency); Map temp = new HashMap<>(); for (LocalDate date : latePaymentCompoundingMap.keySet()) { - if (date.isBefore(lastRestDate)) { + if (DateUtils.isBefore(date, lastRestDate)) { Money money = latePaymentCompoundingMap.get(date); appliedOnPrincipalVariationMap = appliedOnPrincipalVariationMap.plus(money); if (appliedOnPrincipalVariationMap.isLessThan(compoundedPortion)) { - if (date.isBefore(applicableDate)) { + if (DateUtils.isBefore(date, applicableDate)) { updateMapWithAmount(principalVariationMap, money.negated(), date); updateMapWithAmount(principalVariationMap, money, applicableDate); } @@ -1663,14 +1634,14 @@ private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationT Money totalCompoundingAmount = Money.zero(currency); for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { if (loanRepaymentScheduleInstallment.isNotFullyPaidOff() - && !loanRepaymentScheduleInstallment.getDueDate().isAfter(scheduledDueDate) + && !DateUtils.isAfter(loanRepaymentScheduleInstallment.getDueDate(), scheduledDueDate) && !loanRepaymentScheduleInstallment.isRecalculatedInterestComponent()) { LocalDate principalEffectiveDate = loanRepaymentScheduleInstallment.getDueDate(); if (applyRestFrequencyForPrincipal) { principalEffectiveDate = getNextRestScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); } - if (principalEffectiveDate.isBefore(currentDate)) { + if (DateUtils.isBefore(principalEffectiveDate, currentDate)) { updateMapWithAmount(latePaymentMap, loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency), principalEffectiveDate); totalCompoundingAmount = totalCompoundingAmount @@ -1694,15 +1665,12 @@ private void updateCompoundingMap(final LoanApplicationTerms loanApplicationTerm for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : params.getInstallments()) { if (params.getCompoundingDateVariations().containsKey(loanRepaymentScheduleInstallment.getFromDate())) { lastInstallmentIsPastDate = params.applyInterestRecalculation() - && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getBusinessLocalDate()); + && DateUtils.isBeforeBusinessDate(loanRepaymentScheduleInstallment.getDueDate()); } else { final boolean isPastDate = params.applyInterestRecalculation() - && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getBusinessLocalDate()); + && DateUtils.isBeforeBusinessDate(loanRepaymentScheduleInstallment.getDueDate()); boolean periodHasCompoundingDate = false; - Money amountCharged = Money.zero(currency); - if (loanApplicationTerms.getInterestRecalculationCompoundingMethod() != null) { - amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment); - } + Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment); final Map compoundingMap = params.getCompoundingMap(); LocalDate effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate(); if (loanApplicationTerms.allowCompoundingOnEod()) { @@ -1712,7 +1680,7 @@ private void updateCompoundingMap(final LoanApplicationTerms loanApplicationTerm holidayDetailDTO); final LocalDate restDate = getNextRestScheduleDate(scheduledDueDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); - if (!compoundingEffectiveDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())) { + if (!DateUtils.isAfter(compoundingEffectiveDate, loanRepaymentScheduleInstallment.getDueDate())) { Money amountCompoundedFromLastPeriod = params.getCompoundedInLastInstallment(); if (amountCompoundedFromLastPeriod.isZero()) { amountCompoundedFromLastPeriod = params.getUnCompoundedAmount(); @@ -1720,15 +1688,14 @@ private void updateCompoundingMap(final LoanApplicationTerms loanApplicationTerm totalCompoundedAmount = totalCompoundedAmount.minus(amountCompoundedFromLastPeriod); periodHasCompoundingDate = true; } - while (!compoundingEffectiveDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())) { - if (compoundingEffectiveDate.isEqual(loanRepaymentScheduleInstallment.getDueDate())) { + while (!DateUtils.isAfter(compoundingEffectiveDate, loanRepaymentScheduleInstallment.getDueDate())) { + if (DateUtils.isEqual(compoundingEffectiveDate, loanRepaymentScheduleInstallment.getDueDate())) { Money amountToBeCompounding = amountCharged.minus(totalCompoundedAmount); updateMapWithAmount(compoundingMap, amountToBeCompounding, compoundingEffectiveDate); totalCompoundedAmount = totalCompoundedAmount.plus(amountToBeCompounding); - } else if (compoundingMap.containsKey(compoundingEffectiveDate)) { - Money compounedAmount = compoundingMap.get(compoundingEffectiveDate); - totalCompoundedAmount = totalCompoundedAmount.plus(compounedAmount); + Money compoundedAmount = compoundingMap.get(compoundingEffectiveDate); + totalCompoundedAmount = totalCompoundedAmount.plus(compoundedAmount); } if (!loanApplicationTerms.allowCompoundingOnEod()) { @@ -1753,7 +1720,7 @@ private void updateCompoundingMap(final LoanApplicationTerms loanApplicationTerm params.getCompoundingDateVariations().put(loanRepaymentScheduleInstallment.getFromDate(), new TreeMap<>(params.getCompoundingMap())); for (Map.Entry mapEntry : params.getCompoundingMap().entrySet()) { - if (!mapEntry.getKey().isAfter(loanRepaymentScheduleInstallment.getDueDate())) { + if (!DateUtils.isAfter(mapEntry.getKey(), loanRepaymentScheduleInstallment.getDueDate())) { updateMapWithAmount(params.getPrincipalPortionMap(), mapEntry.getValue().negated(), mapEntry.getKey()); } } @@ -1780,7 +1747,6 @@ private Money getIncomeForCompounding(final LoanApplicationTerms loanApplication Money interestCharged = Money.zero(currency); Money feeCharged = Money.zero(currency); Money penaltyCharged = Money.zero(currency); - Money amountCharged = Money.zero(currency); switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) { case INTEREST: interestCharged = interestCharged.plus(loanRepaymentScheduleInstallment.getInterestCharged(currency)); @@ -1797,8 +1763,7 @@ private Money getIncomeForCompounding(final LoanApplicationTerms loanApplication default: break; } - amountCharged = interestCharged.plus(feeCharged).plus(penaltyCharged); - return amountCharged; + return interestCharged.plus(feeCharged).plus(penaltyCharged); } private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate, @@ -1820,7 +1785,7 @@ private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams param private void adjustCompoundedAmountWithPaidDetail(final Map principalPortionMap, final LocalDate lastRestDate, final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency) { - if (!amountApplicableDate.isEqual(lastRestDate)) { + if (!DateUtils.isEqual(amountApplicableDate, lastRestDate)) { Money compoundedIncome = fetchCompoundedArrears(loanApplicationTerms, currency, transaction); updateMapWithAmount(principalPortionMap, compoundedIncome, amountApplicableDate); updateMapWithAmount(principalPortionMap, compoundedIncome.negated(), lastRestDate); @@ -1829,23 +1794,24 @@ private void adjustCompoundedAmountWithPaidDetail(final Map pr private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - final LoanScheduleParams scheduleParams, final Set charges, MonetaryCurrency currency) { + final LoanScheduleParams scheduleParams, final Set charges, MonetaryCurrency currency, final MathContext mc) { if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { final Map compoundingMap = scheduleParams.getCompoundingMap(); LocalDate lastCompoundingDate = startDate; LocalDate compoundingDate = startDate; boolean addUncompounded = true; - while (compoundingDate.isBefore(endDate)) { + while (DateUtils.isBefore(compoundingDate, endDate)) { if (loanApplicationTerms.allowCompoundingOnEod()) { compoundingDate = compoundingDate.minusDays(1); } compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO); - if (compoundingDate.isBefore(endDate)) { + if (DateUtils.isBefore(compoundingDate, endDate)) { + boolean isFirst = DateUtils.isEqual(startDate, lastCompoundingDate); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, - null, loanApplicationTerms.getPrincipal(), null, false, lastCompoundingDate.isEqual(startDate)); + null, loanApplicationTerms.getPrincipal(), null, false, isFirst, mc); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate, compoundingDate, charges, - currency, null, loanApplicationTerms.getPrincipal(), null, false, lastCompoundingDate.isEqual(startDate)); + currency, null, loanApplicationTerms.getPrincipal(), null, false, isFirst, mc); Money compoundAmount = feeChargesForInstallment.plus(penaltyChargesForInstallment); if (addUncompounded) { compoundAmount = compoundAmount.plus(scheduleParams.getUnCompoundedAmount()); @@ -1881,8 +1847,7 @@ private void updatePrincipalPaidPortionToMap(final LoanApplicationTerms loanAppl * */ private TreeMap mergeVariationsToMap(final LoanScheduleParams params) { - TreeMap map = new TreeMap<>(); - map.putAll(params.getLatePaymentMap()); + TreeMap map = new TreeMap<>(params.getLatePaymentMap()); for (Map.Entry mapEntry : params.getDisburseDetailMap().entrySet()) { Money value = mapEntry.getValue(); if (map.containsKey(mapEntry.getKey())) { @@ -1912,23 +1877,22 @@ private TreeMap mergeVariationsToMap(final LoanScheduleParams /** * calculates Interest stating date as per the settings * - * @param firstRepaymentdate + * @param firstRepaymentDate * TODO */ private LocalDate calculateInterestStartDateForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate periodStartDate, - final LocalDate idealDisbursementDate, final LocalDate firstRepaymentdate, + final LocalDate idealDisbursementDate, final LocalDate firstRepaymentDate, final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, final LocalDate expectedDisbursementDate) { LocalDate periodStartDateApplicableForInterest = periodStartDate; - if (periodStartDate.isBefore(idealDisbursementDate) || firstRepaymentdate.isAfter(periodStartDate)) { + if (DateUtils.isBefore(periodStartDate, idealDisbursementDate) || DateUtils.isBefore(periodStartDate, firstRepaymentDate)) { if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { - if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate()) - || loanApplicationTerms.getInterestChargedFromLocalDate().isAfter(periodStartDate)) { + if (DateUtils.isEqual(periodStartDate, loanApplicationTerms.getExpectedDisbursementDate()) + || DateUtils.isBefore(periodStartDate, loanApplicationTerms.getInterestChargedFromLocalDate())) { periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate(); } - } else if (loanApplicationTerms.getInterestChargedFromLocalDate() == null - && isInterestChargedFromDateSameAsDisbursalDateEnabled) { + } else if (isInterestChargedFromDateSameAsDisbursalDateEnabled) { periodStartDateApplicableForInterest = expectedDisbursementDate; - } else if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate())) { + } else if (DateUtils.isEqual(periodStartDate, loanApplicationTerms.getExpectedDisbursementDate())) { periodStartDateApplicableForInterest = idealDisbursementDate; } } @@ -1944,17 +1908,18 @@ private void updateMapWithAmount(final Map map, final Money am } + // Abstract methods + public abstract ScheduledDateGenerator getScheduledDateGenerator(); + + public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator(); + public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, - double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, + BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap principalVariation, Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, Collection termVariations); - protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) { - return periodNumber == numberOfRepayments; - } - private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set loanCharges) { BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO; for (final LoanCharge loanCharge : loanCharges) { @@ -1966,50 +1931,33 @@ private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set periods, final BigDecimal chargesDueAtTimeOfDisbursement, - final Map disburseDetails, final boolean excludePastUndisbursed, LoanScheduleParams scheduleParams) { + final Map disburseDetails, final boolean excludePastUnDisbursed) { + // this method relates to multi-disbursement loans - MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); - if (Objects.isNull(scheduleParams)) { - scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), - loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms)); - } BigDecimal principal = BigDecimal.ZERO; - scheduleParams.setOutstandingBalance(Money.zero(currency)); if (loanApplicationTerms.getDisbursementDatas().size() == 0) { // non tranche loans have no disbursement data entries in submitted and approved status // the appropriate approved amount or applied for amount is used to show a proposed schedule if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) { principal = loanApplicationTerms.getApprovedPrincipal().getAmount(); - scheduleParams.addOutstandingBalance(Money.of(currency, principal)); } else { principal = loanApplicationTerms.getPrincipal().getAmount(); - scheduleParams.addOutstandingBalance(Money.of(currency, principal)); } } else { + MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { if (disbursementData.disbursementDate().equals(disbursementDate)) { - final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( - disbursementData.disbursementDate(), Money.of(currency, disbursementData.getPrincipal()), - chargesDueAtTimeOfDisbursement); - periods.add(disbursementPeriod); - scheduleParams.addOutstandingBalance(Money.of(currency, disbursementData.getPrincipal())); - if (loanApplicationTerms.isDownPaymentEnabled()) { - createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, disbursementData.disbursementDate(), - disbursementData.getPrincipal()); - } - principal = principal.add(disbursementData.getPrincipal()); - } else if (!excludePastUndisbursed || disbursementData.isDisbursed() - || !disbursementData.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) { + } else if (!excludePastUnDisbursed || disbursementData.isDisbursed() + || !DateUtils.isBeforeBusinessDate(disbursementData.disbursementDate())) { /* * JW: sums up amounts by disbursal date in case of side-effect issues. Original assumed that there * were no duplicate disbursal dates and 'put' each amount into the map keyed by date */ - Money prevsum = disburseDetails.get(disbursementData.disbursementDate()); + Money previousSum = disburseDetails.get(disbursementData.disbursementDate()); BigDecimal sumToNow = BigDecimal.ZERO; - if (prevsum != null) { - sumToNow = prevsum.getAmount(); + if (previousSum != null) { + sumToNow = previousSum.getAmount(); } sumToNow = sumToNow.add(disbursementData.getPrincipal()); disburseDetails.put(disbursementData.disbursementDate(), Money.of(currency, sumToNow)); @@ -2019,52 +1967,64 @@ private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicat return principal; } - private Collection createNewLoanScheduleListWithDisbursementDetails(final int numberOfRepayments, - final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement, - LoanScheduleParams scheduleParams) { - Collection periods; - if (loanApplicationTerms.isMultiDisburseLoan()) { - int multiDisbursalTrancheEntries = loanApplicationTerms.getDisbursementDatas().size(); - if (multiDisbursalTrancheEntries == 0) { - // will be zero for non-tranche multi-disbursal loan when submitted or approved - multiDisbursalTrancheEntries = 1; - } - periods = new ArrayList<>(numberOfRepayments + multiDisbursalTrancheEntries); - } else { - periods = new ArrayList<>(numberOfRepayments + 1); + private List createNewLoanScheduleListWithDisbursementDetails(final LoanApplicationTerms loanApplicationTerms, + final LoanScheduleParams loanScheduleParams, final BigDecimal chargesDueAtTimeOfDisbursement) { + List periods = new ArrayList<>(); + if (!loanApplicationTerms.isMultiDisburseLoan()) { final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod .disbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement); periods.add(disbursementPeriod); if (loanApplicationTerms.isDownPaymentEnabled()) { - createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, loanApplicationTerms.getExpectedDisbursementDate(), + final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms, + loanScheduleParams, loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal().getAmount()); + periods.add(downPaymentPeriod); + } + } else { + if (loanApplicationTerms.getDisbursementDatas().isEmpty()) { + loanApplicationTerms.getDisbursementDatas() + .add(new DisbursementData(1L, loanApplicationTerms.getExpectedDisbursementDate(), + loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal().getAmount(), null, + null, null, null)); + } + for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { + if (disbursementData.disbursementDate().equals(loanScheduleParams.getPeriodStartDate())) { + final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( + disbursementData.disbursementDate(), + Money.of(loanScheduleParams.getCurrency(), disbursementData.getPrincipal()), chargesDueAtTimeOfDisbursement); + periods.add(disbursementPeriod); + if (loanApplicationTerms.isDownPaymentEnabled()) { + final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms, + loanScheduleParams, loanApplicationTerms.getExpectedDisbursementDate(), disbursementData.getPrincipal()); + periods.add(downPaymentPeriod); + } + } } } return periods; } - private void createDownPaymentPeriod(LoanApplicationTerms loanApplicationTerms, LoanScheduleParams scheduleParams, - Collection periods, LocalDate date, BigDecimal periodBaseAmount) { + private LoanScheduleModelDownPaymentPeriod createDownPaymentPeriod(LoanApplicationTerms loanApplicationTerms, + LoanScheduleParams scheduleParams, LocalDate date, BigDecimal periodBaseAmount) { BigDecimal downPaymentAmount = MathUtil.percentageOf(periodBaseAmount, loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } Money downPayment = Money.of(loanApplicationTerms.getCurrency(), downPaymentAmount); LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod .downPayment(scheduleParams.getInstalmentNumber(), date, downPayment, scheduleParams.getOutstandingBalance()); addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); - scheduleParams.incrementPeriodNumber(); scheduleParams.incrementInstalmentNumber(); - - periods.add(installment); - - scheduleParams.reduceOutstandingBalance(downPayment); - scheduleParams.addTotalCumulativePrincipal(downPayment); scheduleParams.addTotalRepaymentExpected(downPayment); + + return installment; } - private Set seperateTotalCompoundingPercentageCharges(final Set loanCharges) { + private Set separateTotalCompoundingPercentageCharges(final Set loanCharges) { Set interestCharges = new HashSet<>(); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isSpecifiedDueDate() && (loanCharge.getChargeCalculation().isPercentageOfInterest() @@ -2078,7 +2038,8 @@ private Set seperateTotalCompoundingPercentageCharges(final Set loanCharges, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, - final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod) { + final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, + final MathContext mc) { Money cumulative = Money.zero(monetaryCurrency); @@ -2087,12 +2048,12 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge); + cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, - cumulative, loanCharge); + cumulative, loanCharge, mc); } else if (isDue) { cumulative = cumulative.plus(loanCharge.amount()); } @@ -2103,7 +2064,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L } private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed, - final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge) { + final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge, final MathContext mc) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); @@ -2112,13 +2073,13 @@ private Money calculateSpecificDueDateChargeWithPercentage(final Money principal } else { amount = amount.add(principalDisbursed.getAmount()); } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc); cumulative = cumulative.plus(loanChargeAmt); return cumulative; } private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, Money cumulative, - final LoanCharge loanCharge) { + final LoanCharge loanCharge, final MathContext mc) { if (loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { @@ -2129,7 +2090,7 @@ private Money calculateInstallmentCharge(final PrincipalInterest principalIntere } else { amount = amount.add(principalInterestForThisPeriod.principal().getAmount()); } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc); cumulative = cumulative.plus(loanChargeAmt); } else { cumulative = cumulative.plus(loanCharge.amountOrPercentage()); @@ -2140,7 +2101,8 @@ private Money calculateInstallmentCharge(final PrincipalInterest principalIntere private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, - final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod) { + final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, + final MathContext mc) { Money cumulative = Money.zero(monetaryCurrency); @@ -2149,12 +2111,12 @@ private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, fin boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge); + cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, - cumulative, loanCharge); + cumulative, loanCharge, mc); } else if (isDue) { cumulative = cumulative.plus(loanCharge.amount()); } @@ -2186,8 +2148,6 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L // Loan transactions to process and find the variation on payments Collection recalculationDetails = new ArrayList<>(); List transactions = loan.getLoanTransactions(); - MonetaryCurrency currency = loanApplicationTerms.getCurrency(); - final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loan.getActiveCharges()); for (LoanTransaction loanTransaction : transactions) { if (loanTransaction.isPaymentTransaction()) { recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), @@ -2196,32 +2156,31 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L } final boolean applyInterestRecalculation = loanApplicationTerms.isInterestRecalculationEnabled(); - LoanScheduleParams loanScheduleParams = null; - Collection periods = new ArrayList<>(); + // for complete schedule generation + LoanScheduleParams loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails, + loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); + + List periods = new ArrayList<>(); final List retainedInstallments = new ArrayList<>(); // this block is to retain the schedule installments prior to the // provided date and creates late and early payment details for further // calculations - if (Objects.isNull(rescheduleFrom) && loanApplicationTerms.isDownPaymentEnabled()) { - rescheduleFrom = loanApplicationTerms.getExpectedDisbursementDate(); - } if (rescheduleFrom != null) { Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms); // actual outstanding balance for interest calculation Money outstandingBalance = principalToBeScheduled; + loanScheduleParams.setOutstandingBalance(outstandingBalance); // total outstanding balance as per rest for interest calculation. Money outstandingBalanceAsPerRest = outstandingBalance; + loanScheduleParams.setOutstandingBalanceAsPerRest(outstandingBalanceAsPerRest); // this is required to update total fee amounts in the // LoanScheduleModel - if (loanApplicationTerms.isDownPaymentEnabled()) { - loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, - Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(), - getPrincipalToBeScheduled(loanApplicationTerms)); - } - periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(), - loanApplicationTerms, chargesDueAtTimeOfDisbursement, loanScheduleParams); + final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loan.getActiveCharges()); + periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, loanScheduleParams, + chargesDueAtTimeOfDisbursement); + MonetaryCurrency currency = outstandingBalance.getCurrency(); // early payments will be added here and as per the selected // strategy @@ -2271,8 +2230,19 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) ? loanApplicationTerms.getExpectedDisbursementDate() : loanApplicationTerms.getSubmittedOnDate(); + BigDecimal downPaymentAmount = BigDecimal.ZERO; + if (loanApplicationTerms.isDownPaymentEnabled()) { + double downPaymentAmt = MathUtil.percentageOf(loanScheduleParams.getOutstandingBalance().getAmount(), + loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19).doubleValue(); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } + downPaymentAmount = BigDecimal.valueOf(downPaymentAmt); + } + Money calculatedAmortizableAmount = principalToBeScheduled.minus(downPaymentAmount); + loanScheduleParams.setOutstandingBalance(calculatedAmortizableAmount); // Set fixed Amortization Amounts(either EMI or Principal ) - updateAmortization(mc, loanApplicationTerms, periodNumber, outstandingBalance); + updateAmortization(mc, loanApplicationTerms, periodNumber, calculatedAmortizableAmount); // count periods without interest grace to exclude for flat loan // calculations @@ -2283,7 +2253,7 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L // tranche // details to map BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), - periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true, loanScheduleParams); + disburseDetailMap, true); outstandingBalance = outstandingBalance.zero().plus(disburseAmt); outstandingBalanceAsPerRest = outstandingBalance; principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt); @@ -2307,7 +2277,7 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L // this will generate the next schedule due date and allows to // process the installment only if recalculate from date is // greater than due date - if (installment.getDueDate().isAfter(lastInstallmentDate)) { + if (DateUtils.isAfter(installment.getDueDate(), lastInstallmentDate)) { if (totalCumulativePrincipal.isGreaterThanOrEqualTo(loanApplicationTerms.getTotalDisbursedAmount())) { break; } @@ -2316,14 +2286,14 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L // check for date changes do { - actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, + actualRepaymentDate = getScheduledDateGenerator().generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment); - if (actualRepaymentDate.isAfter(rescheduleFrom) || actualRepaymentDate.isEqual(rescheduleFrom)) { + if (!DateUtils.isBefore(actualRepaymentDate, rescheduleFrom)) { actualRepaymentDate = lastInstallmentDate; } isFirstRepayment = false; LocalDate prevLastInstDate = lastInstallmentDate; - lastInstallmentDate = this.scheduledDateGenerator + lastInstallmentDate = getScheduledDateGenerator() .adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate(); LocalDate modifiedLastInstDate = null; LoanTermVariationsData variation1 = null; @@ -2337,14 +2307,14 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L } } - if (hasDueDateVariation && !lastInstallmentDate.isEqual(installment.getDueDate()) - && !installment.getDueDate().equals(modifiedLastInstDate)) { + if (hasDueDateVariation && !DateUtils.isEqual(lastInstallmentDate, installment.getDueDate()) + && !DateUtils.isEqual(modifiedLastInstDate, installment.getDueDate())) { lastInstallmentDate = prevLastInstDate; actualRepaymentDate = lastInstallmentDate; if (modifiedLastInstDate != null) { loanApplicationTerms.getLoanTermVariations().previousDueDateVariation(); } - } else if (installment.getDueDate().equals(modifiedLastInstDate)) { + } else if (DateUtils.isEqual(modifiedLastInstDate, installment.getDueDate())) { actualRepaymentDate = modifiedLastInstDate; lastInstallmentDate = actualRepaymentDate; dueDateVariationsDataList.add(variation1); @@ -2352,7 +2322,7 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L loanTermVariationParams = applyExceptionLoanTermVariations(loanApplicationTerms, lastInstallmentDate, exceptionDataListIterator, instalmentNumber, totalCumulativePrincipal, totalCumulativeInterest, mc); - } while (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod()); + } while (loanTermVariationParams != null && loanTermVariationParams.skipPeriod()); periodNumber++; @@ -2360,8 +2330,8 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L dueDateVariation.setProcessed(true); } - if (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod()) { - ArrayList variationsDataList = loanTermVariationParams.getVariationsDataList(); + if (loanTermVariationParams != null && loanTermVariationParams.skipPeriod()) { + List variationsDataList = loanTermVariationParams.variationsData(); for (LoanTermVariationsData variationsData : variationsDataList) { variationsData.setProcessed(true); } @@ -2369,27 +2339,26 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L } for (Map.Entry disburseDetail : disburseDetailMap.entrySet()) { - if (disburseDetail.getKey().isAfter(installment.getFromDate()) - && !disburseDetail.getKey().isAfter(installment.getDueDate())) { + if (DateUtils.isAfter(disburseDetail.getKey(), installment.getFromDate()) + && !DateUtils.isAfter(disburseDetail.getKey(), installment.getDueDate())) { // creates and add disbursement detail to the repayments // period final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod .disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement); periods.add(disbursementPeriod); + + BigDecimal downPaymentAmt = BigDecimal.ZERO; if (loanApplicationTerms.isDownPaymentEnabled()) { - if (!disburseDetail.getKey().isEqual(installment.getDueDate())) { - loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, - Money.of(currency, chargesDueAtTimeOfDisbursement), - loanApplicationTerms.getExpectedDisbursementDate(), - getPrincipalToBeScheduled(loanApplicationTerms)); - createDownPaymentPeriod(loanApplicationTerms, loanScheduleParams, periods, disburseDetail.getKey(), - disbursementPeriod.principalDue()); - } + final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms, + loanScheduleParams, disburseDetail.getKey(), disburseDetail.getValue().getAmount()); + periods.add(downPaymentPeriod); + downPaymentAmt = downPaymentPeriod.principalDue(); } // updates actual outstanding balance with new // disbursement detail - outstandingBalance = outstandingBalance.plus(disburseDetail.getValue()); - principalToBeScheduled = principalToBeScheduled.plus(disburseDetail.getValue()); + Money remainingPrincipal = disburseDetail.getValue().minus(downPaymentAmt); + outstandingBalance = outstandingBalance.plus(remainingPrincipal); + principalToBeScheduled = principalToBeScheduled.plus(remainingPrincipal); } } @@ -2401,22 +2370,6 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency)); final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance); periods.add(loanScheduleModelPeriod); - - // downpayment installment for overlapping case - - if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.isDownPaymentEnabled()) { - for (Map.Entry disburseDetail : disburseDetailMap.entrySet()) { - if (disburseDetail.getKey().isAfter(installment.getFromDate()) - && disburseDetail.getKey().isEqual(installment.getDueDate())) { - loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, - Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(), - getPrincipalToBeScheduled(loanApplicationTerms)); - createDownPaymentPeriod(loanApplicationTerms, loanScheduleParams, periods, disburseDetail.getKey(), - disburseDetail.getValue().getAmount()); - } - } - } - totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency)); totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency)); totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency)); @@ -2459,7 +2412,7 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L // recalculatedInterestComponent installment shouldn't be // considered while calculating fixed EMI amounts int period = periodNumber; - if (!lastInstallmentDate.isEqual(installment.getDueDate())) { + if (!DateUtils.isEqual(lastInstallmentDate, installment.getDueDate())) { period--; } reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal, @@ -2512,21 +2465,9 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getLoanSummary().getTotalInterestCharged())); } else { loanApplicationTerms.getLoanTermVariations().resetVariations(); - } - - } - // for complete schedule generation - if (loanScheduleParams == null) { - if (loanApplicationTerms.isDownPaymentEnabled()) { - loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, - Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(), - getPrincipalToBeScheduled(loanApplicationTerms)); - periods.clear(); - } else { - loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails, - loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); periods.clear(); } + } if (retainedInstallments.size() > 0 @@ -2544,21 +2485,21 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L } } periods.addAll(loanScheduleModel.getPeriods()); - LoanScheduleModel loanScheduleModelwithPeriodChanges = LoanScheduleModel.withLoanScheduleModelPeriods(periods, loanScheduleModel); - return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelwithPeriodChanges); + LoanScheduleModel loanScheduleModelWithPeriodChanges = LoanScheduleModel.withLoanScheduleModelPeriods(periods, loanScheduleModel); + return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelWithPeriodChanges); } private List fetchRetainedInstallments( final List repaymentScheduleInstallments, final LocalDate rescheduleFrom, MonetaryCurrency currency) { List newRepaymentScheduleInstallments = new ArrayList<>(); - int lastInterestAvilablePeriod = 0; + int lastInterestAvailablePeriod = 0; int processedPeriod = 0; for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) { - if (installment.getDueDate().isBefore(rescheduleFrom)) { + if (DateUtils.isBefore(installment.getDueDate(), rescheduleFrom)) { newRepaymentScheduleInstallments.add(installment); if (installment.getInterestCharged(currency).isGreaterThanZero()) { - lastInterestAvilablePeriod = installment.getInstallmentNumber(); + lastInterestAvailablePeriod = installment.getInstallmentNumber(); } processedPeriod = installment.getInstallmentNumber(); } else { @@ -2571,10 +2512,10 @@ private List fetchRetainedInstallments( // if the last retained period is interest grace period then we // can't get the interest of last period without calculating again // to fix this adjusting retained periods - if (lastInterestAvilablePeriod != processedPeriod) { + if (lastInterestAvailablePeriod != processedPeriod) { final List retainRepaymentScheduleInstallments = new ArrayList<>(); for (LoanRepaymentScheduleInstallment installment : newRepaymentScheduleInstallments) { - if (installment.getInstallmentNumber() <= lastInterestAvilablePeriod) { + if (installment.getInstallmentNumber() <= lastInterestAvailablePeriod) { retainRepaymentScheduleInstallments.add(installment); } } @@ -2656,7 +2597,7 @@ private Money updateCompoundingDetailsForPartialScheduleGeneration(final LoanRep } compoundingDateVariations.put(installment.getFromDate(), compoundingMap); if (totalCompounded.isGreaterThanZero()) { - final boolean isPastDate = installment.getDueDate().isBefore(DateUtils.getBusinessLocalDate()); + final boolean isPastDate = DateUtils.isBeforeBusinessDate(installment.getDueDate()); final LocalDate restDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); if (isPastDate) { @@ -2749,19 +2690,18 @@ private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(fin private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment, final Money outstandingPrincipal) { final MonetaryCurrency currency = outstandingPrincipal.getCurrency(); - LoanScheduleModelPeriod scheduledLoanInstallment = LoanScheduleModelRepaymentPeriod.repayment(installment.getInstallmentNumber(), - installment.getFromDate(), installment.getDueDate(), installment.getPrincipal(currency), outstandingPrincipal, + return LoanScheduleModelRepaymentPeriod.repayment(installment.getInstallmentNumber(), installment.getFromDate(), + installment.getDueDate(), installment.getPrincipal(currency), outstandingPrincipal, installment.getInterestCharged(currency), installment.getFeeChargesCharged(currency), installment.getPenaltyChargesCharged(currency), installment.getDue(currency), installment.isRecalculatedInterestComponent()); - return scheduledLoanInstallment; } private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) { - LocalDate nextScheduleDate = null; + LocalDate nextScheduleDate; if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { - nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(startDate, + nextScheduleDate = getScheduledDateGenerator().generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(startDate, loanApplicationTerms, holidayDetailDTO); } else { CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); @@ -2773,12 +2713,12 @@ private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTe private LocalDate getNextCompoundScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) { - LocalDate nextScheduleDate = null; + LocalDate nextScheduleDate; if (!loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { return null; } if (loanApplicationTerms.getCompoundingFrequencyType().isSameAsRepayment()) { - nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate, loanApplicationTerms, + nextScheduleDate = getScheduledDateGenerator().generateNextScheduleDateStartingFromDisburseDate(startDate, loanApplicationTerms, holidayDetailDTO); } else { CalendarInstance calendarInstance = loanApplicationTerms.getCompoundingCalendarInstance(); @@ -2826,172 +2766,4 @@ public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final Monetary return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails); } - - private static final class LoanTermVariationParams { - - private final boolean skipPeriod; - private final boolean recalculateAmounts; - private final LocalDate scheduledDueDate; - private final ArrayList variationsData; - - LoanTermVariationParams(final boolean skipPeriod, final boolean recalculateAmounts, final LocalDate scheduledDueDate, - final ArrayList variationsData) { - this.skipPeriod = skipPeriod; - this.recalculateAmounts = recalculateAmounts; - this.scheduledDueDate = scheduledDueDate; - this.variationsData = variationsData; - } - - public boolean isSkipPeriod() { - return this.skipPeriod; - } - - public boolean isRecalculateAmounts() { - return this.recalculateAmounts; - } - - public LocalDate getScheduledDueDate() { - return this.scheduledDueDate; - } - - public ArrayList getVariationsDataList() { - return this.variationsData; - } - - } - - private static final class ScheduleCurrentPeriodParams { - - Money earlyPaidAmount; - LoanScheduleModelPeriod lastInstallment; - boolean skipCurrentLoop; - Money interestForThisPeriod; - Money principalForThisPeriod; - Money feeChargesForInstallment; - Money penaltyChargesForInstallment; - // for adjusting outstandingBalances - Money reducedBalance; - boolean isEmiAmountChanged; - double interestCalculationGraceOnRepaymentPeriodFraction; - - ScheduleCurrentPeriodParams(final MonetaryCurrency currency, double interestCalculationGraceOnRepaymentPeriodFraction) { - this.earlyPaidAmount = Money.zero(currency); - this.lastInstallment = null; - this.skipCurrentLoop = false; - this.interestForThisPeriod = Money.zero(currency); - this.principalForThisPeriod = Money.zero(currency); - this.reducedBalance = Money.zero(currency); - this.feeChargesForInstallment = Money.zero(currency); - this.penaltyChargesForInstallment = Money.zero(currency); - this.isEmiAmountChanged = false; - this.interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction; - } - - public Money getEarlyPaidAmount() { - return this.earlyPaidAmount; - } - - public void plusEarlyPaidAmount(Money earlyPaidAmount) { - this.earlyPaidAmount = this.earlyPaidAmount.plus(earlyPaidAmount); - } - - public void minusEarlyPaidAmount(Money earlyPaidAmount) { - this.earlyPaidAmount = this.earlyPaidAmount.minus(earlyPaidAmount); - } - - public LoanScheduleModelPeriod getLastInstallment() { - return this.lastInstallment; - } - - public void setLastInstallment(LoanScheduleModelPeriod lastInstallment) { - this.lastInstallment = lastInstallment; - } - - public boolean isSkipCurrentLoop() { - return this.skipCurrentLoop; - } - - public void setSkipCurrentLoop(boolean skipCurrentLoop) { - this.skipCurrentLoop = skipCurrentLoop; - } - - public Money getInterestForThisPeriod() { - return this.interestForThisPeriod; - } - - public void setInterestForThisPeriod(Money interestForThisPeriod) { - this.interestForThisPeriod = interestForThisPeriod; - } - - public void minusInterestForThisPeriod(Money interestForThisPeriod) { - this.interestForThisPeriod = this.interestForThisPeriod.minus(interestForThisPeriod); - } - - public Money getPrincipalForThisPeriod() { - return this.principalForThisPeriod; - } - - public void setPrincipalForThisPeriod(Money principalForThisPeriod) { - this.principalForThisPeriod = principalForThisPeriod; - } - - public void plusPrincipalForThisPeriod(Money principalForThisPeriod) { - this.principalForThisPeriod = this.principalForThisPeriod.plus(principalForThisPeriod); - } - - public void minusPrincipalForThisPeriod(Money principalForThisPeriod) { - this.principalForThisPeriod = this.principalForThisPeriod.minus(principalForThisPeriod); - } - - public Money getReducedBalance() { - return this.reducedBalance; - } - - public void setReducedBalance(Money reducedBalance) { - this.reducedBalance = reducedBalance; - } - - public Money getFeeChargesForInstallment() { - return this.feeChargesForInstallment; - } - - public void setFeeChargesForInstallment(Money feeChargesForInstallment) { - this.feeChargesForInstallment = feeChargesForInstallment; - } - - public void minusFeeChargesForInstallment(Money feeChargesForInstallment) { - this.feeChargesForInstallment = this.feeChargesForInstallment.minus(feeChargesForInstallment); - } - - public Money getPenaltyChargesForInstallment() { - return this.penaltyChargesForInstallment; - } - - public void setPenaltyChargesForInstallment(Money penaltyChargesForInstallment) { - this.penaltyChargesForInstallment = penaltyChargesForInstallment; - } - - public void minusPenaltyChargesForInstallment(Money penaltyChargesForInstallment) { - this.penaltyChargesForInstallment = this.penaltyChargesForInstallment.minus(penaltyChargesForInstallment); - } - - public Money fetchTotalAmountForPeriod() { - return this.principalForThisPeriod.plus(interestForThisPeriod).plus(feeChargesForInstallment) - .plus(penaltyChargesForInstallment); - } - - public boolean isEmiAmountChanged() { - return this.isEmiAmountChanged; - } - - public void setEmiAmountChanged(boolean isEmiAmountChanged) { - this.isEmiAmountChanged = isEmiAmountChanged; - } - - public double getInterestCalculationGraceOnRepaymentPeriodFraction() { - return this.interestCalculationGraceOnRepaymentPeriodFraction; - } - - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java new file mode 100644 index 00000000000..8912fad6568 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java @@ -0,0 +1,629 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; +import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; +import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; +import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams; +import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; +import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; + +public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { + + @Override + public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO) { + // TODO: handle interest calculation + final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); + // generate list of proposed schedule due dates + LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); + LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations() + .fetchLoanTermDueDateVariationsData(loanEndDate); + if (lastDueDateVariation != null) { + loanEndDate = lastDueDateVariation.getDateValue(); + } + loanApplicationTerms.updateLoanEndDate(loanEndDate); + + // determine the total charges due at time of disbursement + final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); + + // setup variables for tracking important facts required for loan + // schedule generation. + + final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); + LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) + ? loanApplicationTerms.getExpectedDisbursementDate() + : loanApplicationTerms.getSubmittedOnDate(); + + LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, + Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms)); + + List periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams, + chargesDueAtTimeOfDisbursement); + + boolean isFirstRepayment = true; + + if (loanApplicationTerms.isMultiDisburseLoan()) { + /* fetches the first tranche amount and also updates other tranche details to map */ + BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(), + scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation()); + BigDecimal downPaymentAmt = BigDecimal.ZERO; + if (loanApplicationTerms.isDownPaymentEnabled()) { + downPaymentAmt = MathUtil.percentageOf(disburseAmt, loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } + } + BigDecimal remainingPrincipalAmt = disburseAmt.subtract(downPaymentAmt); + scheduleParams.setPrincipalToBeScheduled(Money.of(currency, remainingPrincipalAmt)); + loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(remainingPrincipalAmt)); + scheduleParams.setOutstandingBalance(Money.of(currency, remainingPrincipalAmt)); + scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, remainingPrincipalAmt)); + loanApplicationTerms.resetFixedEmiAmount(); + } + + // charges which depends on total loan interest will be added to this + // set and handled separately after all installments generated + final Set nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges); + boolean isNextRepaymentAvailable = true; + + while (!scheduleParams.getOutstandingBalance().isZero()) { + scheduleParams.setActualRepaymentDate(getScheduledDateGenerator() + .generateNextRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment)); + AdjustedDateDetailsDTO adjustedDateDetailsDTO = getScheduledDateGenerator() + .adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO); + scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate()); + isFirstRepayment = false; + LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate(); + + // this block is to generate the schedule till the specified + // date(used for calculating preclosure) + boolean isCompletePeriod = true; + if (scheduleParams.getScheduleTillDate() != null && !scheduledDueDate.isBefore(scheduleParams.getScheduleTillDate())) { + if (!scheduledDueDate.isEqual(scheduleParams.getScheduleTillDate())) { + isCompletePeriod = false; + } + scheduledDueDate = scheduleParams.getScheduleTillDate(); + isNextRepaymentAvailable = false; + } + + ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, BigDecimal.ZERO); + + if (loanApplicationTerms.isMultiDisburseLoan()) { + processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, scheduledDueDate); + } + + if (currentPeriodParams.isSkipCurrentLoop()) { + continue; + } + + // 5 determine principal,interest of repayment period + PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( + getPaymentPeriodsInOneYearCalculator(), currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), + scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()), + scheduleParams.getTotalCumulativeInterest(), loanApplicationTerms.getTotalInterestDue(), + scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), + loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, null, scheduleParams.getCompoundingMap(), null, + scheduledDueDate, null); + + // update cumulative fields for principal + currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal()); + updateOutstandingBalance(scheduleParams, currentPeriodParams); + + if (scheduleParams.getOutstandingBalance().isLessThanZero() || !isNextRepaymentAvailable) { + currentPeriodParams.plusPrincipalForThisPeriod(scheduleParams.getOutstandingBalance()); + scheduleParams.setOutstandingBalance(Money.zero(currency)); + } + + if (!isNextRepaymentAvailable) { + scheduleParams.getDisburseDetailMap().clear(); + } + + // applies charges for the period + applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams, mc); + + // sum up real totalInstallmentDue from components + final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod(); + + // if previous installment is last then add interest to same + // installment + if (currentPeriodParams.getLastInstallment() != null && currentPeriodParams.getPrincipalForThisPeriod().isZero()) { + currentPeriodParams.getLastInstallment().addInterestAmount(currentPeriodParams.getInterestForThisPeriod()); + continue; + } + + // create repayment period from parts + LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(), + scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(), + scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(), + currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(), + totalInstallmentDue, !isCompletePeriod); + if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) { + installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount()); + } + addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); + + if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) { + installment.setEMIFixedSpecificToInstallmentTrue(); + } + + periods.add(installment); + + // Updates principal paid map with efective date for reducing + // the amount from outstanding balance(interest calculation) + updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams, + installment); + + // handle cumulative fields + + scheduleParams.addTotalCumulativePrincipal(currentPeriodParams.getPrincipalForThisPeriod()); + scheduleParams.addTotalRepaymentExpected(totalInstallmentDue); + scheduleParams.addTotalCumulativeInterest(currentPeriodParams.getInterestForThisPeriod()); + scheduleParams.setPeriodStartDate(scheduledDueDate); + scheduleParams.incrementInstalmentNumber(); + scheduleParams.incrementPeriodNumber(); + // if (termVariationParams.isRecalculateAmounts()) { + // loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null); + // loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null); + // adjustInstallmentOrPrincipalAmount(loanApplicationTerms, scheduleParams.getTotalCumulativePrincipal(), + // scheduleParams.getPeriodNumber(), mc); + // } + } + + // determine fees and penalties for charges which depends on total + // loan interest + updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc); + + final BigDecimal totalPrincipalPaid = BigDecimal.ZERO; + final BigDecimal totalOutstanding = BigDecimal.ZERO; + + return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(), + scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid, + scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(), + scheduleParams.getTotalPenaltyChargesCharged().getAmount(), scheduleParams.getTotalRepaymentExpected().getAmount(), + totalOutstanding); + } + + @Override + public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan, + HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + LocalDate rescheduleFrom) { + return null; + } + + @Override + public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, + LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO, + LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + return null; + } + + // Abstract methods + public abstract ScheduledDateGenerator getScheduledDateGenerator(); + + public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator(); + + public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, + BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, + Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, + LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap principalVariation, + Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, + Collection termVariations); + + // Private, internal methods + private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set loanCharges) { + BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO; + for (final LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueAtDisbursement()) { + chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount()); + } + } + return chargesDueAtTimeOfDisbursement; + } + + /** + * this method calculates the principal amount for generating the repayment schedule. + */ + private Money getPrincipalToBeScheduled(final LoanApplicationTerms loanApplicationTerms) { + Money principalToBeScheduled; + if (loanApplicationTerms.isMultiDisburseLoan()) { + if (loanApplicationTerms.getTotalDisbursedAmount().isGreaterThanZero()) { + principalToBeScheduled = loanApplicationTerms.getTotalMultiDisbursedAmount(); + } else if (loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) { + principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal(); + } else { + principalToBeScheduled = loanApplicationTerms.getPrincipal(); + } + } else { + principalToBeScheduled = loanApplicationTerms.getPrincipal(); + } + return principalToBeScheduled; + } + + private List createNewLoanScheduleListWithDisbursementDetails(final LoanApplicationTerms loanApplicationTerms, + final LoanScheduleParams loanScheduleParams, final BigDecimal chargesDueAtTimeOfDisbursement) { + List periods = new ArrayList<>(); + + // In case of `disallowExpectedDisbursementDetails = true`, anyway at least 1 disbursement details must exist + if (loanApplicationTerms.getDisbursementDatas().isEmpty()) { + loanApplicationTerms.getDisbursementDatas() + .add(new DisbursementData(1L, loanApplicationTerms.getExpectedDisbursementDate(), + loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal().getAmount(), null, null, + null, null)); + } + for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { + if (disbursementData.disbursementDate().equals(loanScheduleParams.getPeriodStartDate())) { + final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( + disbursementData.disbursementDate(), Money.of(loanScheduleParams.getCurrency(), disbursementData.getPrincipal()), + chargesDueAtTimeOfDisbursement); + periods.add(disbursementPeriod); + if (loanApplicationTerms.isDownPaymentEnabled()) { + final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms, + loanScheduleParams, loanApplicationTerms.getExpectedDisbursementDate(), disbursementData.getPrincipal()); + periods.add(downPaymentPeriod); + } + } + } + + return periods; + } + + private LoanScheduleModelDownPaymentPeriod createDownPaymentPeriod(LoanApplicationTerms loanApplicationTerms, + LoanScheduleParams scheduleParams, LocalDate date, BigDecimal periodBaseAmount) { + BigDecimal downPaymentAmount = MathUtil.percentageOf(periodBaseAmount, + loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } + Money downPayment = Money.of(loanApplicationTerms.getCurrency(), downPaymentAmount); + LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod + .downPayment(scheduleParams.getInstalmentNumber(), date, downPayment, scheduleParams.getOutstandingBalance()); + + addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); + + scheduleParams.incrementInstalmentNumber(); + scheduleParams.addTotalRepaymentExpected(downPayment); + + return installment; + } + + private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List installments, + final LoanScheduleModelPeriod scheduledLoanInstallment) { + LoanRepaymentScheduleInstallment installment = null; + if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) { + installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(), + scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), + scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), + scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(), + scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(), + scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod()); + installments.add(installment); + } + return installment; + } + + /** + * Method add extra disbursement periods (if applicable) and update the schedule params + */ + private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement, + LoanScheduleParams scheduleParams, final Collection periods, final LocalDate scheduledDueDate) { + for (Map.Entry disburseDetail : scheduleParams.getDisburseDetailMap().entrySet()) { + if (disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()) + && !disburseDetail.getKey().isAfter(scheduledDueDate)) { + // validation check for amount not exceeds specified max + // amount as per the configuration + loanApplicationTerms.getMaxOutstandingBalance(); + if (scheduleParams.getOutstandingBalance().plus(disburseDetail.getValue()) + .isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) { + String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance(); + throw new MultiDisbursementOutstandingAmoutException(errorMsg, + loanApplicationTerms.getMaxOutstandingBalance().getAmount(), disburseDetail.getValue()); + } + + // creates and add disbursement detail to the repayments + // period + final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod + .disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement); + periods.add(disbursementPeriod); + + BigDecimal downPaymentAmt = BigDecimal.ZERO; + if (loanApplicationTerms.isDownPaymentEnabled()) { + final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms, + scheduleParams, disburseDetail.getKey(), disburseDetail.getValue().getAmount()); + periods.add(downPaymentPeriod); + downPaymentAmt = downPaymentPeriod.principalDue(); + } + // updates actual outstanding balance with new + // disbursement detail + Money remainingPrincipal = disburseDetail.getValue().minus(downPaymentAmt); + scheduleParams.addOutstandingBalance(remainingPrincipal); + scheduleParams.addOutstandingBalanceAsPerRest(remainingPrincipal); + scheduleParams.addPrincipalToBeScheduled(remainingPrincipal); + loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(remainingPrincipal)); + loanApplicationTerms.resetFixedEmiAmount(); + } + } + } + + private void applyChargesForCurrentPeriod(final Set loanCharges, final MonetaryCurrency currency, + LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams, + final MathContext mc) { + PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(), + currentPeriodParams.getInterestForThisPeriod(), null); + currentPeriodParams.setFeeChargesForInstallment(cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(), scheduledDueDate, + loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), + scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); + currentPeriodParams.setPenaltyChargesForInstallment(cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(), + scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), + scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); + scheduleParams.addTotalFeeChargesCharged(currentPeriodParams.getFeeChargesForInstallment()); + scheduleParams.addTotalPenaltyChargesCharged(currentPeriodParams.getPenaltyChargesForInstallment()); + } + + private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, + final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, + final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, + final MathContext mc) { + Money cumulative = Money.zero(monetaryCurrency); + for (final LoanCharge loanCharge : loanCharges) { + if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { + cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed, + totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc); + } + } + return cumulative; + } + + private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod, + Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, + boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) { + boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) + : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); + } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { + cumulative = cumulative.plus(loanCharge.chargeAmount()); + } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { + cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, cumulative, + loanCharge, mc); + } else if (isDue) { + cumulative = cumulative.plus(loanCharge.amount()); + } + return cumulative; + } + + private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, + final Set loanCharges, final MonetaryCurrency monetaryCurrency, + final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, + final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, + final MathContext mc) { + Money cumulative = Money.zero(monetaryCurrency); + for (final LoanCharge loanCharge : loanCharges) { + if (loanCharge.isPenaltyCharge()) { + cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed, + totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc); + } + } + return cumulative; + } + + private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, Money cumulative, + final LoanCharge loanCharge, final MathContext mc) { + if (loanCharge.getChargeCalculation().isPercentageBased()) { + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { + amount = amount.add(principalInterestForThisPeriod.principal().getAmount()) + .add(principalInterestForThisPeriod.interest().getAmount()); + } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { + amount = amount.add(principalInterestForThisPeriod.interest().getAmount()); + } else { + amount = amount.add(principalInterestForThisPeriod.principal().getAmount()); + } + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc); + cumulative = cumulative.plus(loanChargeAmt); + } else { + cumulative = cumulative.plus(loanCharge.amountOrPercentage()); + } + return cumulative; + } + + private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed, + final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge, final MathContext mc) { + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { + amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); + } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { + amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount()); + } else { + amount = amount.add(principalDisbursed.getAmount()); + } + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc); + cumulative = cumulative.plus(loanChargeAmt); + return cumulative; + } + + private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams, + LoanScheduleModelPeriod installment) { + LocalDate amountApplicableDate = installment.periodDueDate(); + if (loanApplicationTerms.isInterestRecalculationEnabled()) { + amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + } + updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), + currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate); + + // update outstanding balance for interest calculation + updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate); + } + + private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, + final HolidayDetailDTO holidayDetailDTO) { + LocalDate nextScheduleDate; + if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { + nextScheduleDate = getScheduledDateGenerator().generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(startDate, + loanApplicationTerms, holidayDetailDTO); + } else { + CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); + nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate); + } + + return nextScheduleDate; + } + + private void updateMapWithAmount(final Map map, final Money amount, final LocalDate amountApplicableDate) { + Money principalPaid = amount; + if (map.containsKey(amountApplicableDate)) { + principalPaid = map.get(amountApplicableDate).plus(principalPaid); + } + map.put(amountApplicableDate, principalPaid); + + } + + private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) { + scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getPrincipalPortionMap(), + scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), false)); + } + + /** + * Identifies all the past date principal changes and apply them on outstanding balance for future calculations + */ + private Money updateBalanceForInterestCalculation(final Map principalPortionMap, final LocalDate scheduledDueDate, + final Money outstandingBalanceAsPerRest, boolean addMapDetails) { + List removeFromPrincipalPortionMap = new ArrayList<>(); + Money outstandingBalance = outstandingBalanceAsPerRest; + for (Map.Entry principal : principalPortionMap.entrySet()) { + if (!principal.getKey().isAfter(scheduledDueDate)) { + if (addMapDetails) { + outstandingBalance = outstandingBalance.plus(principal.getValue()); + } else { + outstandingBalance = outstandingBalance.minus(principal.getValue()); + } + removeFromPrincipalPortionMap.add(principal.getKey()); + } + } + for (LocalDate date : removeFromPrincipalPortionMap) { + principalPortionMap.remove(date); + } + return outstandingBalance; + } + + private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams, + final Collection periods, final Set nonCompoundingCharges, MathContext mc) { + for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { + if (loanScheduleModelPeriod.isRepaymentPeriod()) { + PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()), + Money.of(currency, loanScheduleModelPeriod.interestDue()), null); + Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), + loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, + scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), + !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc); + Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), + loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, + scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), + !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc); + scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment); + scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment); + scheduleParams.addTotalRepaymentExpected(feeChargesForInstallment.plus(penaltyChargesForInstallment)); + loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount()); + } + } + } + + private Set separateTotalCompoundingPercentageCharges(final Set loanCharges) { + Set interestCharges = new HashSet<>(); + for (final LoanCharge loanCharge : loanCharges) { + if (loanCharge.isSpecifiedDueDate() && (loanCharge.getChargeCalculation().isPercentageOfInterest() + || loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest())) { + interestCharges.add(loanCharge); + } + } + loanCharges.removeAll(interestCharges); + return interestCharges; + } + + private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms, LocalDate disbursementDate, + final Map disburseDetails, final boolean excludePastUnDisbursed) { + + // this method relates to multi-disbursement loans + BigDecimal principal = BigDecimal.ZERO; + if (loanApplicationTerms.getDisbursementDatas().size() == 0) { + // non tranche loans have no disbursement data entries in submitted and approved status + // the appropriate approved amount or applied for amount is used to show a proposed schedule + if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) { + principal = loanApplicationTerms.getApprovedPrincipal().getAmount(); + } else { + principal = loanApplicationTerms.getPrincipal().getAmount(); + } + } else { + MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); + for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { + if (disbursementData.disbursementDate().equals(disbursementDate)) { + principal = principal.add(disbursementData.getPrincipal()); + } else if (!excludePastUnDisbursed || disbursementData.isDisbursed() + || !disbursementData.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) { + /* + * JW: sums up amounts by disbursal date in case of side-effect issues. Original assumed that there + * were no duplicate disbursal dates and 'put' each amount into the map keyed by date + */ + Money previousSum = disburseDetails.get(disbursementData.disbursementDate()); + BigDecimal sumToNow = BigDecimal.ZERO; + if (previousSum != null) { + sumToNow = previousSum.getAmount(); + } + sumToNow = sumToNow.add(disbursementData.getPrincipal()); + disburseDetails.put(disbursementData.disbursementDate(), Money.of(currency, sumToNow)); + } + } + } + return principal; + } + + private void updateOutstandingBalance(LoanScheduleParams scheduleParams, ScheduleCurrentPeriodParams currentPeriodParams) { + // update outstandingLoanBlance using current period + // 'principalDue' + scheduleParams + .reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance())); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java similarity index 90% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java rename to fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java index 3d00b33dff6..53c0cbe2cf4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java @@ -26,9 +26,12 @@ import java.util.HashMap; import java.util.Map; import java.util.TreeMap; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; +import org.springframework.stereotype.Component; /** *

@@ -53,11 +56,26 @@ * minus interest due. *

*/ -public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator { +@Component +@RequiredArgsConstructor +public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator { + + private final ScheduledDateGenerator scheduledDateGenerator; + private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; + + @Override + public ScheduledDateGenerator getScheduledDateGenerator() { + return scheduledDateGenerator; + } + + @Override + public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() { + return paymentPeriodsInOneYearCalculator; + } @Override public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator, - final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, + final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, @SuppressWarnings("unused") final Money totalCumulativeInterest, @SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, @@ -89,7 +107,7 @@ public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final Pay for (Map.Entry principal : principalVariation.entrySet()) { - if (!principal.getKey().isAfter(periodEndDate)) { + if (!DateUtils.isAfter(principal.getKey(), periodEndDate)) { int interestForDays = Math.toIntExact(ChronoUnit.DAYS.between(interestStartDate, principal.getKey())); if (interestForDays > 0) { final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java similarity index 82% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java rename to fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java index 78f2ebcad90..852bd9b1254 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java @@ -18,19 +18,37 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; +import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; import java.util.Collection; import java.util.Map; import java.util.TreeMap; +import lombok.RequiredArgsConstructor; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; +import org.springframework.stereotype.Component; -public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator { +@Component +@RequiredArgsConstructor +public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator { + + private final ScheduledDateGenerator scheduledDateGenerator; + private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; + + @Override + public ScheduledDateGenerator getScheduledDateGenerator() { + return scheduledDateGenerator; + } + + @Override + public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() { + return paymentPeriodsInOneYearCalculator; + } @Override public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator, - final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, + final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, Money totalCumulativeInterest, Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, @SuppressWarnings("unused") TreeMap principalVariation, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java index fdad36ea53b..46ea6543ad7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java @@ -18,29 +18,36 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; +import lombok.RequiredArgsConstructor; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGeneratorFactory { - @Override - public LoanScheduleGenerator create(final InterestMethod interestMethod) { + private final ProgressiveLoanScheduleGenerator progressiveLoanScheduleGenerator; + private final CumulativeFlatInterestLoanScheduleGenerator cumulativeFlatInterestLoanScheduleGenerator; + private final CumulativeDecliningBalanceInterestLoanScheduleGenerator cumulativeDecliningBalanceInterestLoanScheduleGenerator; - LoanScheduleGenerator loanScheduleGenerator = null; + @Override + public LoanScheduleGenerator create(final LoanScheduleType loanScheduleType, final InterestMethod interestMethod) { + return switch (loanScheduleType) { + case CUMULATIVE -> cumulativeLoanScheduleGenerator(interestMethod); + case PROGRESSIVE -> progressiveLoanScheduleGenerator(interestMethod); + }; + } - switch (interestMethod) { - case FLAT: - loanScheduleGenerator = new FlatInterestLoanScheduleGenerator(); - break; - case DECLINING_BALANCE: - loanScheduleGenerator = new DecliningBalanceInterestLoanScheduleGenerator(); - break; - case INVALID: - break; - } + private LoanScheduleGenerator cumulativeLoanScheduleGenerator(final InterestMethod interestMethod) { + return switch (interestMethod) { + case FLAT -> cumulativeFlatInterestLoanScheduleGenerator; + case DECLINING_BALANCE -> cumulativeDecliningBalanceInterestLoanScheduleGenerator; + case INVALID -> null; + }; + } - return loanScheduleGenerator; + private LoanScheduleGenerator progressiveLoanScheduleGenerator(final InterestMethod interestMethod) { + return progressiveLoanScheduleGenerator; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java index 0af3bf6c7ef..4c7cc946433 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java @@ -18,90 +18,83 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; +import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import java.time.temporal.ChronoUnit; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +@Component +@Slf4j public class DefaultPaymentPeriodsInOneYearCalculator implements PaymentPeriodsInOneYearCalculator { - private static final Logger LOG = LoggerFactory.getLogger(DefaultPaymentPeriodsInOneYearCalculator.class); - @Override public Integer calculate(final PeriodFrequencyType repaymentFrequencyType) { - Integer paymentPeriodsInOneYear = Integer.valueOf(0); + Integer paymentPeriodsInOneYear = 0; switch (repaymentFrequencyType) { case DAYS: - paymentPeriodsInOneYear = Integer.valueOf(365); + paymentPeriodsInOneYear = 365; break; case WEEKS: - paymentPeriodsInOneYear = Integer.valueOf(52); + paymentPeriodsInOneYear = 52; break; case MONTHS: - paymentPeriodsInOneYear = Integer.valueOf(12); + paymentPeriodsInOneYear = 12; break; case YEARS: - paymentPeriodsInOneYear = Integer.valueOf(1); + paymentPeriodsInOneYear = 1; break; case INVALID: - paymentPeriodsInOneYear = Integer.valueOf(0); + paymentPeriodsInOneYear = 0; break; case WHOLE_TERM: - LOG.error("TODO Implement repaymentFrequencyType for WHOLE_TERM"); + log.error("TODO Implement repaymentFrequencyType for WHOLE_TERM"); break; } return paymentPeriodsInOneYear; } @Override - public double calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate, + public BigDecimal calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate, final LocalDate scheduledDueDate, final LocalDate interestChargedFromLocalDate, - final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer repaidEvery) { + final PeriodFrequencyType repaymentPeriodFrequencyType, final int repaidEvery, MathContext mc) { - Double periodFraction = Double.valueOf("0.0"); + BigDecimal periodFraction = BigDecimal.ZERO; final LocalDateInterval repaymentPeriod = new LocalDateInterval(repaymentPeriodStartDate, scheduledDueDate); if (interestChargedFromLocalDate != null && repaymentPeriod.fallsBefore(interestChargedFromLocalDate.plusDays(1))) { - periodFraction = Double.valueOf("1.0"); + periodFraction = BigDecimal.ONE; } else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) { final int numberOfDaysInterestCalculationGraceInPeriod = Math .toIntExact(ChronoUnit.DAYS.between(repaymentPeriodStartDate, interestChargedFromLocalDate)); periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery, - numberOfDaysInterestCalculationGraceInPeriod); + numberOfDaysInterestCalculationGraceInPeriod, mc); } return periodFraction; } - private double calculateRepaymentPeriodFraction(final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer every, - final Integer numberOfDaysInterestCalculationGrace) { + private BigDecimal calculateRepaymentPeriodFraction(final PeriodFrequencyType repaymentPeriodFrequencyType, final int repaidEvery, + final int numberOfDaysInterestCalculationGrace, final MathContext mc) { - Double fraction = Double.valueOf("0"); - switch (repaymentPeriodFrequencyType) { - case DAYS: - fraction = numberOfDaysInterestCalculationGrace.doubleValue() * every.doubleValue(); - break; - case WEEKS: - fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.parseDouble("7.0") * every.doubleValue()); - break; - case MONTHS: - fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.parseDouble("30.0") * every.doubleValue()); - break; - case YEARS: - fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.parseDouble("365.0") * every.doubleValue()); - break; - case INVALID: - fraction = Double.valueOf("0"); - break; - case WHOLE_TERM: - LOG.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM"); - break; - } - return fraction; + BigDecimal repayEveryBD = BigDecimal.valueOf(repaidEvery); + BigDecimal noDaysInterestCalculationGrace = BigDecimal.valueOf(numberOfDaysInterestCalculationGrace); + return switch (repaymentPeriodFrequencyType) { + case DAYS -> noDaysInterestCalculationGrace.multiply(repayEveryBD, mc); + case WEEKS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(7), mc).multiply(repayEveryBD, mc); + case MONTHS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(30), mc).multiply(repayEveryBD, mc); + case YEARS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(365), mc).multiply(repayEveryBD, mc); + case WHOLE_TERM -> { + log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM"); + yield BigDecimal.ZERO; + } + default -> BigDecimal.ZERO; + }; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index 804cb1c400d..72e529af060 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -20,7 +20,9 @@ import java.time.LocalDate; import java.time.temporal.ChronoUnit; +import lombok.extern.slf4j.Slf4j; import net.fortuna.ical4j.model.Recur; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.service.HolidayUtil; import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; @@ -33,13 +35,12 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +@Component +@Slf4j public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { - private static final Logger LOG = LoggerFactory.getLogger(DefaultScheduledDateGenerator.class); - @Override public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) { @@ -92,7 +93,7 @@ public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, fi seedDate = currentCalendar.getStartDateLocalDate(); reccuringString = currentCalendar.getRecurrence(); } else { - seedDate = calendarHistory.getStartDateLocalDate(); + seedDate = calendarHistory.getStartDate(); reccuringString = calendarHistory.getRecurrence(); } @@ -177,7 +178,7 @@ private void checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(final AdjustedD holidayDetailDTO.getHolidays())) != null) { if (applicableHolidayForNewAdjustedDate.getReScheduleType().isResheduleToNextRepaymentDate()) { LocalDate nextRepaymentPeriodDueDate = adjustedDateDetailsDTO.getChangedActualRepaymentDate(); - while (!nextRepaymentPeriodDueDate.isAfter(adjustedDateDetailsDTO.getChangedScheduleDate())) { + while (!DateUtils.isAfter(nextRepaymentPeriodDueDate, adjustedDateDetailsDTO.getChangedScheduleDate())) { nextRepaymentPeriodDueDate = generateNextRepaymentDate(nextRepaymentPeriodDueDate, loanApplicationTerms, isFirstRepayment); } @@ -208,14 +209,13 @@ private void checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(final Adjust .getRepaymentRescheduleType(holidayDetailDTO.getWorkingDays()); if (repaymentRescheduleType.isMoveToNextRepaymentDay()) { - while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), - adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate()) - || adjustedDateDetailsDTO.getChangedScheduleDate() - .isAfter(adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate())) { - final LocalDate nextRepaymentPeriodDueDate = generateNextRepaymentDate( - adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(), loanApplicationTerms, isFirstRepayment); - adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate); + LocalDate nextRepaymentPeriodDueDate = adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(); + while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), nextRepaymentPeriodDueDate) + || DateUtils.isAfter(adjustedDateDetailsDTO.getChangedScheduleDate(), nextRepaymentPeriodDueDate)) { + nextRepaymentPeriodDueDate = generateNextRepaymentDate(nextRepaymentPeriodDueDate, loanApplicationTerms, + isFirstRepayment); } + adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate); } WorkingDaysUtil.updateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO.getWorkingDays()); } @@ -240,7 +240,7 @@ public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, fin case INVALID: break; case WHOLE_TERM: - LOG.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM"); + log.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM"); break; } return dueRepaymentPeriodDate; @@ -260,7 +260,7 @@ public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final isScheduledDate = (weekDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusWeeks(weekDiff); - isScheduledDate = modifiedDate.isEqual(date); + isScheduledDate = DateUtils.isEqual(modifiedDate, date); } break; case MONTHS: @@ -268,7 +268,7 @@ public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final isScheduledDate = (monthDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusMonths(monthDiff); - isScheduledDate = modifiedDate.isEqual(date); + isScheduledDate = DateUtils.isEqual(modifiedDate, date); } break; case YEARS: @@ -276,13 +276,13 @@ public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final isScheduledDate = (yearDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusYears(yearDiff); - isScheduledDate = modifiedDate.isEqual(date); + isScheduledDate = DateUtils.isEqual(modifiedDate, date); } break; case INVALID: break; case WHOLE_TERM: - LOG.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM"); + log.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM"); break; } return isScheduledDate; @@ -319,7 +319,7 @@ public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFreq case INVALID: break; case WHOLE_TERM: - LOG.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM"); + log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM"); break; } @@ -332,7 +332,7 @@ public LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate last LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate(); boolean isFirstRepayment = true; - while (!generatedDate.isAfter(lastRepaymentDate)) { + while (!DateUtils.isAfter(generatedDate, lastRepaymentDate)) { generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment); isFirstRepayment = false; } @@ -351,7 +351,7 @@ public LocalDate generateNextScheduleDateStartingFromDisburseDateOrRescheduleDat isFirstRepayment = false; } LocalDate adjustedDate = generatedDate; - while (!adjustedDate.isAfter(lastRepaymentDate)) { + while (!DateUtils.isAfter(adjustedDate, lastRepaymentDate)) { generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment); adjustedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate(); isFirstRepayment = false; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java index 8a5104e9cd2..3f9894ea927 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java @@ -40,8 +40,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement) { - final int periodNumber = 0; - return new LoanScheduleModelDisbursementPeriod(periodNumber, loanApplicationTerms.getExpectedDisbursementDate(), + return new LoanScheduleModelDisbursementPeriod(null, loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanTermVariationParams.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanTermVariationParams.java new file mode 100644 index 00000000000..1651c04b314 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanTermVariationParams.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; + +import java.time.LocalDate; +import java.util.List; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; + +public record LoanTermVariationParams(boolean skipPeriod, boolean recalculateAmounts, LocalDate scheduledDueDate, + List variationsData) { +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java new file mode 100644 index 00000000000..99a106e2d01 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.LocalDate; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanScheduleGenerator { + + private final ScheduledDateGenerator scheduledDateGenerator; + private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; + + @Override + public ScheduledDateGenerator getScheduledDateGenerator() { + return scheduledDateGenerator; + } + + @Override + public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() { + return paymentPeriodsInOneYearCalculator; + } + + @Override + public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, + BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, + Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, + LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap principalVariation, + Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, + Collection termVariations) { + // TODO: handle interest calculation + Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance, + periodNumber, mc, Money.zero(loanApplicationTerms.getCurrency())); + final Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment); + // adjust if needed + principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment, + totalCumulativePrincipalToDate, periodNumber); + + return new PrincipalInterest(principalForThisInstallment, Money.zero(loanApplicationTerms.getCurrency()), + Money.zero(loanApplicationTerms.getCurrency())); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduleCurrentPeriodParams.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduleCurrentPeriodParams.java new file mode 100644 index 00000000000..58ac9a9b1c1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduleCurrentPeriodParams.java @@ -0,0 +1,160 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; + +import java.math.BigDecimal; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; + +public class ScheduleCurrentPeriodParams { + + Money earlyPaidAmount; + LoanScheduleModelPeriod lastInstallment; + boolean skipCurrentLoop; + Money interestForThisPeriod; + Money principalForThisPeriod; + Money feeChargesForInstallment; + Money penaltyChargesForInstallment; + // for adjusting outstandingBalances + Money reducedBalance; + boolean isEmiAmountChanged; + BigDecimal interestCalculationGraceOnRepaymentPeriodFraction; + + ScheduleCurrentPeriodParams(final MonetaryCurrency currency) { + this(currency, BigDecimal.ZERO); + } + + ScheduleCurrentPeriodParams(final MonetaryCurrency currency, BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) { + this.earlyPaidAmount = Money.zero(currency); + this.lastInstallment = null; + this.skipCurrentLoop = false; + this.interestForThisPeriod = Money.zero(currency); + this.principalForThisPeriod = Money.zero(currency); + this.reducedBalance = Money.zero(currency); + this.feeChargesForInstallment = Money.zero(currency); + this.penaltyChargesForInstallment = Money.zero(currency); + this.isEmiAmountChanged = false; + this.interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction; + } + + public Money getEarlyPaidAmount() { + return this.earlyPaidAmount; + } + + public void plusEarlyPaidAmount(Money earlyPaidAmount) { + this.earlyPaidAmount = this.earlyPaidAmount.plus(earlyPaidAmount); + } + + public void minusEarlyPaidAmount(Money earlyPaidAmount) { + this.earlyPaidAmount = this.earlyPaidAmount.minus(earlyPaidAmount); + } + + public LoanScheduleModelPeriod getLastInstallment() { + return this.lastInstallment; + } + + public void setLastInstallment(LoanScheduleModelPeriod lastInstallment) { + this.lastInstallment = lastInstallment; + } + + public boolean isSkipCurrentLoop() { + return this.skipCurrentLoop; + } + + public void setSkipCurrentLoop(boolean skipCurrentLoop) { + this.skipCurrentLoop = skipCurrentLoop; + } + + public Money getInterestForThisPeriod() { + return this.interestForThisPeriod; + } + + public void setInterestForThisPeriod(Money interestForThisPeriod) { + this.interestForThisPeriod = interestForThisPeriod; + } + + public void minusInterestForThisPeriod(Money interestForThisPeriod) { + this.interestForThisPeriod = this.interestForThisPeriod.minus(interestForThisPeriod); + } + + public Money getPrincipalForThisPeriod() { + return this.principalForThisPeriod; + } + + public void setPrincipalForThisPeriod(Money principalForThisPeriod) { + this.principalForThisPeriod = principalForThisPeriod; + } + + public void plusPrincipalForThisPeriod(Money principalForThisPeriod) { + this.principalForThisPeriod = this.principalForThisPeriod.plus(principalForThisPeriod); + } + + public void minusPrincipalForThisPeriod(Money principalForThisPeriod) { + this.principalForThisPeriod = this.principalForThisPeriod.minus(principalForThisPeriod); + } + + public Money getReducedBalance() { + return this.reducedBalance; + } + + public void setReducedBalance(Money reducedBalance) { + this.reducedBalance = reducedBalance; + } + + public Money getFeeChargesForInstallment() { + return this.feeChargesForInstallment; + } + + public void setFeeChargesForInstallment(Money feeChargesForInstallment) { + this.feeChargesForInstallment = feeChargesForInstallment; + } + + public void minusFeeChargesForInstallment(Money feeChargesForInstallment) { + this.feeChargesForInstallment = this.feeChargesForInstallment.minus(feeChargesForInstallment); + } + + public Money getPenaltyChargesForInstallment() { + return this.penaltyChargesForInstallment; + } + + public void setPenaltyChargesForInstallment(Money penaltyChargesForInstallment) { + this.penaltyChargesForInstallment = penaltyChargesForInstallment; + } + + public void minusPenaltyChargesForInstallment(Money penaltyChargesForInstallment) { + this.penaltyChargesForInstallment = this.penaltyChargesForInstallment.minus(penaltyChargesForInstallment); + } + + public Money fetchTotalAmountForPeriod() { + return this.principalForThisPeriod.plus(interestForThisPeriod).plus(feeChargesForInstallment).plus(penaltyChargesForInstallment); + } + + public boolean isEmiAmountChanged() { + return this.isEmiAmountChanged; + } + + public void setEmiAmountChanged(boolean isEmiAmountChanged) { + this.isEmiAmountChanged = isEmiAmountChanged; + } + + public BigDecimal getInterestCalculationGraceOnRepaymentPeriodFraction() { + return this.interestCalculationGraceOnRepaymentPeriodFraction; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index f0e31f2cee2..ee35a790c14 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -98,6 +98,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; @@ -431,7 +432,7 @@ private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( LoanEnumerations.loanVariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), periodData.getInterestRate(), dateValue, isSpecificToInstallment); - if (!interestRateStartDate.isBefore(periodData.getFromDateAsLocalDate())) { + if (!DateUtils.isBefore(interestRateStartDate, periodData.getFromDateAsLocalDate())) { interestRateStartDate = periodData.getFromDateAsLocalDate(); annualNominalInterestRate = periodData.getInterestRate(); } @@ -471,6 +472,7 @@ private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement .isDisableScheduleExtensionForDownPayment(); } + LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType(); return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, @@ -485,7 +487,7 @@ private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement allowCompoundingOnEod, isEqualAmortization, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, - isScheduleExtensionForDownPaymentDisabled); + isScheduleExtensionForDownPaymentDisabled, loanScheduleType); } private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery, @@ -666,20 +668,22 @@ public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loa final MathContext mc = new MathContext(8, roundingMode); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); - LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); + LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), + loanApplicationTerms.getInterestMethod()); if (loanApplicationTerms.isEqualAmortization()) { if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) { final LoanScheduleGenerator decliningLoanScheduleGenerator = this.loanScheduleFactory - .create(InterestMethod.DECLINING_BALANCE); + .create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE); LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO); loanApplicationTerms .updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged())); } - loanScheduleGenerator = this.loanScheduleFactory.create(InterestMethod.FLAT); + loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.FLAT); } else { - loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); + loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), + loanApplicationTerms.getInterestMethod()); } return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO); @@ -696,7 +700,8 @@ public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationT loanApplicationTerms.getExpectedDisbursementDate(), HolidayStatusType.ACTIVE.getValue()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); - final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); + final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), + loanApplicationTerms.getInterestMethod()); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loan, detailDTO, loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel(); @@ -705,7 +710,8 @@ public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationT public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { - final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); + final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), + loanApplicationTerms.getInterestMethod()); final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final MathContext mc = new MathContext(8, roundingMode); @@ -753,7 +759,7 @@ public void assempleVariableScheduleFrom(final Loan loan, final String json) { LocalDate lastDate = loan.getExpectedDisbursedOnLocalDate(); for (LoanRepaymentScheduleInstallment installment : installments) { dueDates.add(installment.getDueDate()); - if (lastDate.isBefore(installment.getDueDate())) { + if (DateUtils.isBefore(lastDate, installment.getDueDate())) { lastDate = installment.getDueDate(); } if (graceOnPrincipal.equals(installment.getInstallmentNumber())) { @@ -781,14 +787,14 @@ public void assempleVariableScheduleFrom(final Loan loan, final String json) { } else { dueDates.add(termVariations.fetchTermApplicaDate()); } - if (!graceApplicable.isBefore(termVariations.fetchTermApplicaDate())) { + if (!DateUtils.isBefore(graceApplicable, termVariations.fetchTermApplicaDate())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.insert.not.allowed.before.grace.period", "Loan schedule insert request invalid"); } - if (termVariations.fetchTermApplicaDate().isAfter(lastDate)) { + if (DateUtils.isAfter(termVariations.fetchTermApplicaDate(), lastDate)) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.insert.not.allowed.after.last.period.date", "Loan schedule insert request invalid"); - } else if (termVariations.fetchTermApplicaDate().isBefore(loan.getExpectedDisbursedOnLocalDate())) { + } else if (DateUtils.isBefore(termVariations.fetchTermApplicaDate(), loan.getExpectedDisbursedOnLocalDate())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid"); } @@ -800,14 +806,13 @@ public void assempleVariableScheduleFrom(final Loan loan, final String json) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.remove.date.invalid", "Loan schedule remove request invalid"); } - if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) { + if (DateUtils.isEqual(lastDate, termVariations.fetchTermApplicaDate())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.delete.not.allowed.for.last.period.date", "Loan schedule remove request invalid"); } break; case DUE_DATE: if (dueDates.contains(termVariations.fetchTermApplicaDate())) { - if (overlappings.contains(termVariations.fetchTermApplicaDate())) { overlappings.remove(termVariations.fetchTermApplicaDate()); } else { @@ -822,17 +827,17 @@ public void assempleVariableScheduleFrom(final Loan loan, final String json) { } else { dueDates.add(termVariations.fetchDateValue()); } - if (termVariations.fetchDateValue().isBefore(loan.getExpectedDisbursedOnLocalDate())) { + if (DateUtils.isBefore(termVariations.fetchDateValue(), loan.getExpectedDisbursedOnLocalDate())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid"); } - if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) { + if (DateUtils.isEqual(lastDate, termVariations.fetchTermApplicaDate())) { lastDate = termVariations.fetchDateValue(); } break; case PRINCIPAL_AMOUNT: case EMI_AMOUNT: - if (!graceApplicable.isBefore(termVariations.fetchTermApplicaDate())) { + if (!DateUtils.isBefore(graceApplicable, termVariations.fetchTermApplicaDate())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.amount.update.not.allowed.before.grace.period", "Loan schedule modify request invalid"); } @@ -840,7 +845,7 @@ public void assempleVariableScheduleFrom(final Loan loan, final String json) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.amount.update.from.date.invalid", "Loan schedule modify request invalid"); } - if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) { + if (DateUtils.isEqual(termVariations.fetchTermApplicaDate(), lastDate)) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.amount.update.not.allowed.for.last.period", "Loan schedule modify request invalid"); } @@ -941,7 +946,7 @@ private List adjustExistingVariations(List installments = loan.getRepaymentScheduleInstallments(); for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { if (currentInstallment.isNotFullyPaidOff()) { - if (!currentInstallment.getDueDate().isAfter(today)) { + if (!DateUtils.isAfter(currentInstallment.getDueDate(), today)) { totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency)); } } @@ -190,7 +190,7 @@ public void updateFutureSchedule(LoanScheduleData loanScheduleData, final Long l LoanScheduleData scheduleDate = model.toData(); Collection periodDatas = scheduleDate.getPeriods(); for (LoanSchedulePeriodData periodData : periodDatas) { - if ((periodData.getDueDate().isEqual(today) || periodData.getDueDate().isAfter(today)) && isNewPaymentRequired) { + if (isNewPaymentRequired && !DateUtils.isBefore(periodData.getDueDate(), today)) { LoanSchedulePeriodData loanSchedulePeriodData = LoanSchedulePeriodData.repaymentOnlyPeriod(periodData.getPeriod(), periodData.getFromDate(), periodData.getDueDate(), totalPrincipal.getAmount(), periodData.getPrincipalLoanBalanceOutstanding(), interestDue.getAmount(), @@ -199,7 +199,7 @@ public void updateFutureSchedule(LoanScheduleData loanScheduleData, final Long l totalPrincipal.plus(interestDue).getAmount()); futureInstallments.add(loanSchedulePeriodData); isNewPaymentRequired = false; - } else if (periodData.getDueDate().isAfter(today)) { + } else if (DateUtils.isAfter(periodData.getDueDate(), today)) { futureInstallments.add(periodData); } @@ -239,7 +239,7 @@ private LoanScheduleData constructLoanScheduleData(Loan loan) { for (LoanRepaymentScheduleInstallment installment : installments) { if (loanDisbursementDetails != null - && !loanDisbursementDetails.expectedDisbursementDateAsLocalDate().isAfter(installment.getDueDate())) { + && !DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDateAsLocalDate(), installment.getDueDate())) { outstanding = outstanding.plus(loanDisbursementDetails.principal()); principal = principal.plus(loanDisbursementDetails.principal()); if (disbursementItr.hasNext()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java index 9ed49e01e11..d3628ae0c3d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java @@ -73,7 +73,7 @@ public List createLoanScheduleArchive( final BigDecimal penaltyCharges = repaymentScheduleInstallment.getPenaltyChargesCharged(currency).getAmount(); Map oldDates = null; - OffsetDateTime createdOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); + OffsetDateTime createdOnDate = DateUtils.getAuditOffsetDateTime(); LocalDateTime oldCreatedOnDate = null; LocalDateTime oldLastModifiedOnDate = null; if (repaymentScheduleInstallment.getCreatedDate().isPresent()) { @@ -89,7 +89,7 @@ public List createLoanScheduleArchive( final Long lastModifiedByUser = repaymentScheduleInstallment.getLastModifiedBy() .orElse(platformSecurityContext.authenticatedUser().getId()); - OffsetDateTime lastModifiedOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(); + OffsetDateTime lastModifiedOnDate = DateUtils.getAuditOffsetDateTime(); if (repaymentScheduleInstallment.getLastModifiedDate().isPresent()) { lastModifiedOnDate = repaymentScheduleInstallment.getLastModifiedDate().get(); } else if (repaymentScheduleInstallment.getId() != null && oldDates == null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResourceSwagger.java index 1f1840cde96..cf704ba6998 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResourceSwagger.java @@ -40,7 +40,7 @@ static final class GetRescheduleReasonsAllowedTypes { private GetRescheduleReasonsAllowedTypes() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "reason") public String name; @Schema(example = "0") @@ -66,7 +66,7 @@ static final class GetLoanRescheduleRequestStatus { private GetLoanRescheduleRequestStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "loanStatusType.submitted.and.pending.approval") public String code; @Schema(example = "Submitted and pending approval") @@ -84,7 +84,7 @@ static final class RescheduleReasonsCodeValue { private RescheduleReasonsCodeValue() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "reason") public String name; @Schema(example = "true") @@ -120,7 +120,7 @@ static final class LoanTermTypeOptions { private LoanTermTypeOptions() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "loanTermType.dueDate") public String code; @Schema(example = "dueDate") @@ -132,7 +132,7 @@ static final class LoanTermVariationsData { private LoanTermVariationsData() {} @Schema(example = "1") - public Integer id; + public Long id; public LoanTermTypeOptions termType; @Schema(example = "[2022, 5, 1]") public LocalDate termVariationApplicableFrom; @@ -148,11 +148,11 @@ private LoanTermVariationsData() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "18") - public Integer loanId; + public Long loanId; @Schema(example = "15") - public Integer clientId; + public Long clientId; @Schema(example = "clientName") public String clientName; @Schema(example = "000000018") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java index 328d1ca2b4c..7f6f57abfa1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java @@ -38,6 +38,7 @@ import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -105,7 +106,7 @@ public void validateForCreateAction(final JsonCommand jsonCommand, final Loan lo jsonElement); dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull(); - if (submittedOnDate != null && loan.getDisbursementDate().isAfter(submittedOnDate)) { + if (submittedOnDate != null && DateUtils.isAfter(loan.getDisbursementDate(), submittedOnDate)) { dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName) .failWithCode("before.loan.disbursement.date", "Submission date cannot be before the loan disbursement date"); } @@ -161,7 +162,7 @@ public void validateForCreateAction(final JsonCommand jsonCommand, final Loan lo final LocalDate adjustedDueDate = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName, jsonElement); - if (adjustedDueDate != null && rescheduleFromDate != null && adjustedDueDate.isBefore(rescheduleFromDate)) { + if (adjustedDueDate != null && DateUtils.isBefore(adjustedDueDate, rescheduleFromDate)) { dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode( "adjustedDueDate.before.rescheduleFromDate", "Adjusted due date cannot be before the reschedule from date"); } @@ -212,7 +213,7 @@ private void validateForOverdueCharges(DataValidatorBuilder dataValidatorBuilder LocalDate rescheduleFromDate = installment.getFromDate(); Collection charges = loan.getLoanCharges(); for (LoanCharge loanCharge : charges) { - if (loanCharge.isOverdueInstallmentCharge() && loanCharge.getDueLocalDate().isAfter(rescheduleFromDate)) { + if (loanCharge.isOverdueInstallmentCharge() && DateUtils.isAfter(loanCharge.getDueLocalDate(), rescheduleFromDate)) { dataValidatorBuilder.failWithCodeNoParameterAddedToErrorCode("not.allowed.due.to.overdue.charges"); break; } @@ -247,7 +248,7 @@ public void validateForApproveAction(final JsonCommand jsonCommand, LoanReschedu jsonElement); dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull(); - if (approvedOnDate != null && loanRescheduleRequest.getSubmittedOnDate().isAfter(approvedOnDate)) { + if (approvedOnDate != null && DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), approvedOnDate)) { dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).failWithCode("before.submission.date", "Approval date cannot be before the request submission date."); } @@ -319,7 +320,7 @@ public void validateForRejectAction(final JsonCommand jsonCommand, LoanReschedul jsonElement); dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam).value(rejectedOnDate).notNull(); - if (rejectedOnDate != null && loanRescheduleRequest.getSubmittedOnDate().isAfter(rejectedOnDate)) { + if (rejectedOnDate != null && DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), rejectedOnDate)) { dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam).failWithCode("before.submission.date", "Rejection date cannot be before the request submission date."); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index ac794d877aa..d326f5a7cb3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; @@ -113,10 +114,10 @@ public LoanScheduleModel previewLoanReschedule(Long requestId) { } for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) { - if (rescheduleFromDate.isBefore(loanTermVariation.getTermVariationApplicableFrom())) { + if (DateUtils.isBefore(rescheduleFromDate, loanTermVariation.getTermVariationApplicableFrom())) { LocalDate applicableDate = DEFAULT_SCHEDULED_DATE_GENERATOR.generateNextRepaymentDate(rescheduleFromDate, loanApplicationTerms, false); - if (loanTermVariation.getTermVariationApplicableFrom().equals(applicableDate)) { + if (DateUtils.isEqual(loanTermVariation.getTermVariationApplicableFrom(), applicableDate)) { LocalDate adjustedDate = DEFAULT_SCHEDULED_DATE_GENERATOR.generateNextRepaymentDate(adjustedApplicableDate, loanApplicationTerms, false); loanTermVariation.setApplicableFromDate(adjustedDate); @@ -130,7 +131,8 @@ public LoanScheduleModel previewLoanReschedule(Long requestId) { final MathContext mathContext = new MathContext(8, roundingMode); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory .determineProcessor(loan.transactionProcessingStrategy()); - final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); + final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), + loanApplicationTerms.getInterestMethod()); loan.setHelpers(null, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory); final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan, loanApplicationTerms.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, rescheduleFromDate); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 27d798cf974..7a35d2210ee 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -272,12 +272,12 @@ private void createLoanTermVariationsForRegularLoans(final Loan loan, final Inte final Integer termType = LoanTermVariationType.EMI_AMOUNT.getValue(); List installments = loan.getRepaymentScheduleInstallments(); for (LoanRepaymentScheduleInstallment installment : installments) { - if (installment.getDueDate().isEqual(rescheduleFromLocDate) || installment.getDueDate().isEqual(endDateLocDate) - || (installment.getDueDate().isAfter(rescheduleFromLocDate) && installment.getDueDate().isBefore(endDateLocDate))) { + if (!DateUtils.isBefore(installment.getDueDate(), rescheduleFromLocDate) + && !DateUtils.isAfter(installment.getDueDate(), endDateLocDate)) { createLoanTermVariations(loanRescheduleRequest, termType, loan, installment.getDueDate(), installment.getDueDate(), loanRescheduleRequestToTermVariationMappings, isActive, true, emi, parent); } - if (installment.getDueDate().isAfter(endDateLocDate)) { + if (DateUtils.isAfter(installment.getDueDate(), endDateLocDate)) { break; } } @@ -394,8 +394,8 @@ public CommandProcessingResult approve(JsonCommand jsonCommand) { activeLoanTermVariation.markAsInactive(); rescheduleFromDate = activeLoanTermVariation.fetchTermApplicaDate(); dueDateVariationInCurrentRequest.setTermApplicableFrom(rescheduleFromDate); - } else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) { - while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) { + } else if (!DateUtils.isBefore(activeLoanTermVariation.fetchTermApplicaDate(), fromScheduleDate)) { + while (DateUtils.isBefore(currentScheduleDate, activeLoanTermVariation.fetchTermApplicaDate())) { currentScheduleDate = DEFAULT_SCHEDULED_DATE_GENERATOR.generateNextRepaymentDate(currentScheduleDate, loanApplicationTerms, false); modifiedScheduleDate = DEFAULT_SCHEDULED_DATE_GENERATOR.generateNextRepaymentDate(modifiedScheduleDate, @@ -436,7 +436,8 @@ public CommandProcessingResult approve(JsonCommand jsonCommand) { final MathContext mathContext = new MathContext(8, roundingMode); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory .determineProcessor(loan.transactionProcessingStrategy()); - final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); + final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), + loanApplicationTerms.getInterestMethod()); final LoanLifecycleStateMachine loanLifecycleStateMachine = null; loan.setHelpers(loanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory); final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java index 0b9f35062ef..8252e309d8a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.springframework.beans.factory.annotation.Autowired; @@ -166,8 +167,7 @@ public void validateSelectedPeriodFrequencyTypeIsTheSame(final List dataValidationErrors, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate) { - if (expectedDisbursementDate != null && repaymentsStartingFromDate != null - && expectedDisbursementDate.isAfter(repaymentsStartingFromDate)) { + if (repaymentsStartingFromDate != null && DateUtils.isAfter(expectedDisbursementDate, repaymentsStartingFromDate)) { final ApiParameterError error = ApiParameterError.parameterError( "validation.msg.loan.expectedDisbursementDate.cannot.be.after.first.repayment.date", "The parameter expectedDisbursementDate has a date which falls after the date for repaymentsStartingFromDate.", diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index e0d48ea3b6e..82c73c9ae95 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -41,6 +41,7 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.accountdetails.domain.AccountType; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement; @@ -1182,7 +1183,7 @@ private void validateDisbursementsAreDatewiseOrdered(JsonElement element, final if (jsonObject2.has(LoanApiConstants.expectedDisbursementDateParameterName)) { LocalDate date2 = this.fromApiJsonHelper.extractLocalDateNamed( LoanApiConstants.expectedDisbursementDateParameterName, jsonObject2, dateFormat, locale); - if (date1.isAfter(date2)) { + if (DateUtils.isAfter(date1, date2)) { baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName) .failWithCode(LoanApiConstants.DISBURSEMENT_DATES_NOT_IN_ORDER); } @@ -1196,7 +1197,6 @@ private void validateDisbursementsAreDatewiseOrdered(JsonElement element, final public void validateLoanMultiDisbursementDate(final JsonElement element, final DataValidatorBuilder baseDataValidator, LocalDate expectedDisbursement, BigDecimal totalPrincipal) { - this.validateDisbursementsAreDatewiseOrdered(element, baseDataValidator); final JsonObject topLevelJsonElement = element.getAsJsonObject(); @@ -1228,7 +1228,8 @@ public void validateLoanMultiDisbursementDate(final JsonElement element, final D if (i == 0 && expectedDisbursementDate != null && !expectedDisbursement.equals(expectedDisbursementDate)) { baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName) .failWithCode(LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR); - } else if (i > 0 && expectedDisbursementDate != null && expectedDisbursementDate.isBefore(expectedDisbursement)) { + } else if (i > 0 && expectedDisbursementDate != null + && DateUtils.isBefore(expectedDisbursementDate, expectedDisbursement)) { baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName) .failWithCode(LoanApiConstants.DISBURSEMENT_DATE_BEFORE_ERROR); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java index 20e8ac58583..48ba7dca4a5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java @@ -32,9 +32,7 @@ import org.apache.fineract.portfolio.group.domain.GroupingTypeStatus; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class BulkLoansReadPlatformServiceImpl implements BulkLoansReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/GLIMAccountInfoReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/GLIMAccountInfoReadPlatformServiceImpl.java index 76a9936df36..82b630e9a72 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/GLIMAccountInfoReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/GLIMAccountInfoReadPlatformServiceImpl.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData; import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService; @@ -32,27 +33,16 @@ import org.apache.fineract.portfolio.loanaccount.data.GlimRepaymentTemplate; import org.apache.fineract.portfolio.loanaccount.data.GroupLoanIndividualMonitoringAccountData; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class GLIMAccountInfoReadPlatformServiceImpl implements GLIMAccountInfoReadPlatformService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; private final AccountDetailsReadPlatformService accountDetailsReadPlatforService; - @Autowired - public GLIMAccountInfoReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate, - final AccountDetailsReadPlatformService accountDetailsReadPlatforService) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - this.accountDetailsReadPlatforService = accountDetailsReadPlatforService; - - } - private static final class GLIMFieldsMapper implements RowMapper { public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/GLIMAccountInfoWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/GLIMAccountInfoWritePlatformServiceImpl.java index 23ca79539ac..f07da8036b0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/GLIMAccountInfoWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/GLIMAccountInfoWritePlatformServiceImpl.java @@ -20,15 +20,14 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.math.BigDecimal; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class GLIMAccountInfoWritePlatformServiceImpl implements GLIMAccountInfoWritePlatformService { private final PlatformSecurityContext context; @@ -37,14 +36,6 @@ public class GLIMAccountInfoWritePlatformServiceImpl implements GLIMAccountInfoW private final LoanRepository loanRepository; - @Autowired - public GLIMAccountInfoWritePlatformServiceImpl(final PlatformSecurityContext context, - final GLIMAccountInfoRepository glimAccountRepository, final LoanRepository loanRepository) { - this.context = context; - this.glimAccountRepository = glimAccountRepository; - this.loanRepository = loanRepository; - } - @Override public void addGLIMAccountInfo(String accountNumber, Group group, BigDecimal principalAmount, Long childAccountsCount, Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java index abe7867f3ea..85a6a8dcfc9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java @@ -29,9 +29,7 @@ import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.springframework.stereotype.Service; -@Service @Slf4j @RequiredArgsConstructor public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualTransactionBusinessEventServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualTransactionBusinessEventServiceImpl.java index 2afa34d7a7a..0a0c8499797 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualTransactionBusinessEventServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualTransactionBusinessEventServiceImpl.java @@ -24,9 +24,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class LoanAccrualTransactionBusinessEventServiceImpl implements LoanAccrualTransactionBusinessEventService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java index e0ab1231a44..7de27a355cf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java @@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; @@ -59,10 +60,8 @@ import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service @RequiredArgsConstructor public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlatformService { @@ -114,12 +113,12 @@ public void addPeriodicAccruals(final LocalDate tillDate, Long loanId, Collectio loanWaiverTransactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId()); } - if (accrualData.getDueDateAsLocaldate().isAfter(tillDate)) { + if (DateUtils.isAfter(accrualData.getDueDateAsLocaldate(), tillDate)) { if (accruedTill == null || firstTime) { accruedTill = accrualData.getAccruedTill(); firstTime = false; } - if (accruedTill == null || accruedTill.isBefore(tillDate)) { + if (accruedTill == null || DateUtils.isBefore(accruedTill, tillDate)) { updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), tillDate); updateInterestIncome(accrualData, loanWaiverTransactionData, loanWaiverScheduleData, tillDate); addAccrualTillSpecificDate(tillDate, accrualData); @@ -135,9 +134,8 @@ public void addPeriodicAccruals(final LocalDate tillDate, Long loanId, Collectio private void addAccrualTillSpecificDate(final LocalDate tillDate, final LoanScheduleAccrualData accrualData) { LocalDate interestStartDate = accrualData.getFromDateAsLocaldate(); - if (accrualData.getInterestCalculatedFrom() != null - && accrualData.getFromDateAsLocaldate().isBefore(accrualData.getInterestCalculatedFrom())) { - if (accrualData.getInterestCalculatedFrom().isBefore(accrualData.getDueDateAsLocaldate())) { + if (DateUtils.isBefore(accrualData.getFromDateAsLocaldate(), accrualData.getInterestCalculatedFrom())) { + if (DateUtils.isBefore(accrualData.getInterestCalculatedFrom(), accrualData.getDueDateAsLocaldate())) { interestStartDate = accrualData.getInterestCalculatedFrom(); } else { interestStartDate = accrualData.getDueDateAsLocaldate(); @@ -146,8 +144,8 @@ private void addAccrualTillSpecificDate(final LocalDate tillDate, final LoanSche int totalNumberOfDays = Math.toIntExact(ChronoUnit.DAYS.between(interestStartDate, accrualData.getDueDateAsLocaldate())); LocalDate startDate = accrualData.getFromDateAsLocaldate(); - if (accrualData.getInterestCalculatedFrom() != null && startDate.isBefore(accrualData.getInterestCalculatedFrom())) { - if (accrualData.getInterestCalculatedFrom().isBefore(tillDate)) { + if (DateUtils.isBefore(startDate, accrualData.getInterestCalculatedFrom())) { + if (DateUtils.isBefore(accrualData.getInterestCalculatedFrom(), tillDate)) { startDate = accrualData.getInterestCalculatedFrom(); } else { startDate = tillDate; @@ -291,7 +289,7 @@ private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, B scheduleAccrualData.getRepaymentScheduleId()); String updateLoan = "UPDATE m_loan SET accrued_till=?, last_modified_by=?, last_modified_on_utc=? WHERE id=?"; - this.jdbcTemplate.update(updateLoan, accruedTill, user.getId(), DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(), + this.jdbcTemplate.update(updateLoan, accruedTill, user.getId(), DateUtils.getAuditOffsetDateTime(), scheduleAccrualData.getLoanId()); businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(loanTransaction)); @@ -380,9 +378,10 @@ private void updateChargeForSubmittedOnDate(Collection chargesDa LocalDate scheduleEndDate = accrualData.getDueDateAsLocaldate(); for (LoanChargeData loanCharge : chargesData) { BigDecimal chargeAmount = BigDecimal.ZERO; - if (((accrualData.getInstallmentNumber() == 1 && loanCharge.getSubmittedOnDate().isEqual(startDate) - && loanCharge.getDueDate().isEqual(startDate)) || loanCharge.getDueDate().isAfter(startDate)) - && !loanCharge.getSubmittedOnDate().isAfter(endDate) && !loanCharge.getDueDate().isAfter(scheduleEndDate)) { + if (((accrualData.getInstallmentNumber() == 1 && DateUtils.isEqual(startDate, loanCharge.getSubmittedOnDate()) + && DateUtils.isEqual(startDate, loanCharge.getDueDate())) || DateUtils.isBefore(startDate, loanCharge.getDueDate())) + && !DateUtils.isBefore(endDate, loanCharge.getSubmittedOnDate()) + && !DateUtils.isBefore(scheduleEndDate, loanCharge.getDueDate())) { chargeAmount = loanCharge.getAmount(); if (loanCharge.getAmountUnrecognized() != null) { chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized()); @@ -424,7 +423,7 @@ private void updateChargeForDueDate(Collection chargesData, Loan for (LoanChargeData loanCharge : chargesData) { BigDecimal chargeAmount = BigDecimal.ZERO; if (loanCharge.getDueDate() == null) { - if (loanCharge.isInstallmentFee() && accrualData.getDueDateAsLocaldate().isEqual(endDate)) { + if (loanCharge.isInstallmentFee() && DateUtils.isEqual(endDate, accrualData.getDueDateAsLocaldate())) { Collection installmentData = loanCharge.getInstallmentChargeData(); for (LoanInstallmentChargeData installmentChargeData : installmentData) { @@ -452,8 +451,8 @@ private void updateChargeForDueDate(Collection chargesData, Loan } } } - } else if (((accrualData.getInstallmentNumber() == 1 && loanCharge.getDueDate().isEqual(startDate)) - || loanCharge.getDueDate().isAfter(startDate)) && !loanCharge.getDueDate().isAfter(endDate)) { + } else if (((accrualData.getInstallmentNumber() == 1 && DateUtils.isEqual(loanCharge.getDueDate(), startDate)) + || DateUtils.isAfter(loanCharge.getDueDate(), startDate)) && !DateUtils.isAfter(loanCharge.getDueDate(), endDate)) { chargeAmount = loanCharge.getAmount(); if (loanCharge.getAmountUnrecognized() != null) { chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized()); @@ -501,22 +500,23 @@ private void updateInterestIncome(final LoanScheduleAccrualData accrualData, Collection loanTransactionDatas = new ArrayList<>(); for (LoanTransactionData loanTransactionData : loanWaiverTransactions) { - if (!loanTransactionData.getDate().isAfter(accrualData.getFromDateAsLocaldate()) - || (loanTransactionData.getDate().isAfter(accrualData.getFromDateAsLocaldate()) - && !loanTransactionData.getDate().isAfter(accrualData.getDueDateAsLocaldate()) - && !loanTransactionData.getDate().isAfter(tillDate))) { + LocalDate transactionDate = loanTransactionData.getDate(); + if (!DateUtils.isAfter(transactionDate, accrualData.getFromDateAsLocaldate()) + || (DateUtils.isAfter(transactionDate, accrualData.getFromDateAsLocaldate()) + && !DateUtils.isAfter(transactionDate, accrualData.getDueDateAsLocaldate()) + && !DateUtils.isAfter(transactionDate, tillDate))) { loanTransactionDatas.add(loanTransactionData); } } Iterator iterator = loanTransactionDatas.iterator(); for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDataList) { - if (recognized.compareTo(BigDecimal.ZERO) <= 0 && unrecognized.compareTo(BigDecimal.ZERO) <= 0 && iterator.hasNext()) { + if (MathUtil.isLessThanOrEqualZero(recognized) && MathUtil.isLessThanOrEqualZero(unrecognized) && iterator.hasNext()) { LoanTransactionData loanTransactionData = iterator.next(); recognized = recognized.add(loanTransactionData.getInterestPortion()); unrecognized = unrecognized.add(loanTransactionData.getUnrecognizedIncomePortion()); } - if (loanSchedulePeriodData.getDueDate().isBefore(accrualData.getDueDateAsLocaldate())) { + if (DateUtils.isBefore(loanSchedulePeriodData.getDueDate(), accrualData.getDueDateAsLocaldate())) { remainingAmt = remainingAmt.add(loanSchedulePeriodData.getInterestWaived()); if (recognized.compareTo(remainingAmt) > 0) { recognized = recognized.subtract(remainingAmt); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 13696fc18fe..925031c7a3a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -53,6 +53,7 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.dataqueries.data.EntityTables; import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum; @@ -144,11 +145,9 @@ import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -@Service @Slf4j @RequiredArgsConstructor public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements LoanApplicationWritePlatformService { @@ -289,7 +288,7 @@ public CommandProcessingResult submitApplication(final JsonCommand command) { "Topup on loan with multi-tranche disbursal and without interest recalculation is not supported."); } final LocalDate disbursalDateOfLoanToClose = loanToClose.getDisbursementDate(); - if (!newLoanApplication.getSubmittedOnDate().isAfter(disbursalDateOfLoanToClose)) { + if (!DateUtils.isAfter(newLoanApplication.getSubmittedOnDate(), disbursalDateOfLoanToClose)) { throw new GeneralPlatformDomainRuleException( "error.msg.loan.submitted.date.should.be.after.topup.loan.disbursal.date", "Submitted date of this loan application " + newLoanApplication.getSubmittedOnDate() @@ -300,7 +299,7 @@ public CommandProcessingResult submitApplication(final JsonCommand command) { "loanIdToClose is invalid, Currency code is different."); } final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate(); - if (newLoanApplication.getDisbursementDate().isBefore(lastUserTransactionOnLoanToClose)) { + if (DateUtils.isBefore(newLoanApplication.getDisbursementDate(), lastUserTransactionOnLoanToClose)) { throw new GeneralPlatformDomainRuleException( "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed", "Disbursal date of this loan application " + newLoanApplication.getDisbursementDate() @@ -869,7 +868,7 @@ public CommandProcessingResult modifyApplication(final Long loanId, final JsonCo "Topup on loan with multi-tranche disbursal and without interest recalculation is not supported."); } final LocalDate disbursalDateOfLoanToClose = loanToClose.getDisbursementDate(); - if (!existingLoanApplication.getSubmittedOnDate().isAfter(disbursalDateOfLoanToClose)) { + if (!DateUtils.isAfter(existingLoanApplication.getSubmittedOnDate(), disbursalDateOfLoanToClose)) { throw new GeneralPlatformDomainRuleException( "error.msg.loan.submitted.date.should.be.after.topup.loan.disbursal.date", "Submitted date of this loan application " + existingLoanApplication.getSubmittedOnDate() @@ -880,7 +879,7 @@ public CommandProcessingResult modifyApplication(final Long loanId, final JsonCo "loanIdToClose is invalid, Currency code is different."); } final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate(); - if (existingLoanApplication.getDisbursementDate().isBefore(lastUserTransactionOnLoanToClose)) { + if (DateUtils.isBefore(existingLoanApplication.getDisbursementDate(), lastUserTransactionOnLoanToClose)) { throw new GeneralPlatformDomainRuleException( "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed", "Disbursal date of this loan application " + existingLoanApplication.getDisbursementDate() @@ -1382,7 +1381,7 @@ public CommandProcessingResult approveApplication(final Long loanId, final JsonC } final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate(); - if (loan.getDisbursementDate().isBefore(lastUserTransactionOnLoanToClose)) { + if (DateUtils.isBefore(loan.getDisbursementDate(), lastUserTransactionOnLoanToClose)) { throw new GeneralPlatformDomainRuleException( "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed", "Disbursal date of this loan application " + loan.getDisbursementDate() @@ -1636,19 +1635,19 @@ private void validateSubmittedOnDate(final Loan loan) { final LocalDate submittedOnDate = loan.getSubmittedOnDate(); String defaultUserMessage = ""; - if (startDate != null && submittedOnDate.isBefore(startDate)) { + if (DateUtils.isBefore(submittedOnDate, startDate)) { defaultUserMessage = "submittedOnDate cannot be before the loan product startDate."; throw new LoanApplicationDateException("submitted.on.date.cannot.be.before.the.loan.product.start.date", defaultUserMessage, submittedOnDate.toString(), startDate.toString()); } - if (closeDate != null && submittedOnDate.isAfter(closeDate)) { + if (closeDate != null && DateUtils.isAfter(submittedOnDate, closeDate)) { defaultUserMessage = "submittedOnDate cannot be after the loan product closeDate."; throw new LoanApplicationDateException("submitted.on.date.cannot.be.after.the.loan.product.close.date", defaultUserMessage, submittedOnDate.toString(), closeDate.toString()); } - if (expectedFirstRepaymentOnDate != null && submittedOnDate.isAfter(expectedFirstRepaymentOnDate)) { + if (expectedFirstRepaymentOnDate != null && DateUtils.isAfter(submittedOnDate, expectedFirstRepaymentOnDate)) { defaultUserMessage = "submittedOnDate cannot be after the loans expectedFirstRepaymentOnDate."; throw new LoanApplicationDateException("submitted.on.date.cannot.be.after.the.loan.expected.first.repayment.date", defaultUserMessage, submittedOnDate.toString(), expectedFirstRepaymentOnDate.toString()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java index 0401aa61967..f0a245267f0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java @@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.event.business.BusinessEventListener; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; @@ -58,9 +59,7 @@ import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.stereotype.Service; -@Service @Slf4j @RequiredArgsConstructor public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService { @@ -147,12 +146,12 @@ private String constructUpdateStatement(final Loan loan, boolean isInsertStateme LocalDate businessDate = DateUtils.getBusinessLocalDate(); LocalDate overDueSince = businessDate; for (LoanRepaymentScheduleInstallment installment : installments) { - if (installment.getDueDate().isBefore(businessDate)) { + if (DateUtils.isBefore(installment.getDueDate(), businessDate)) { principalOverdue = principalOverdue.add(installment.getPrincipalOutstanding(loan.getCurrency()).getAmount()); interestOverdue = interestOverdue.add(installment.getInterestOutstanding(loan.getCurrency()).getAmount()); feeOverdue = feeOverdue.add(installment.getFeeChargesOutstanding(loan.getCurrency()).getAmount()); penaltyOverdue = penaltyOverdue.add(installment.getPenaltyChargesOutstanding(loan.getCurrency()).getAmount()); - if (installment.isNotFullyPaidOff() && overDueSince.isAfter(installment.getDueDate())) { + if (installment.isNotFullyPaidOff() && DateUtils.isAfter(overDueSince, installment.getDueDate())) { overDueSince = installment.getDueDate(); } } @@ -210,8 +209,8 @@ public void createInsertStatements(List insertStatement, Map 0) { + if (DateUtils.isAfter(overDueSince, loanSchedulePeriodData.getDueDate()) && MathUtil + .isGreaterThan(loanSchedulePeriodData.getPrincipalDue(), loanSchedulePeriodData.getPrincipalPaid())) { overDueSince = loanSchedulePeriodData.getDueDate(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java index 78a8eead90b..2264b9c852d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java @@ -86,9 +86,7 @@ import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.rate.service.RateAssembler; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class LoanAssembler { @@ -308,6 +306,8 @@ private Loan assembleApplication(final JsonElement element, final Long clientId, } } + loanApplication.updateEnableInstallmentLevelDelinquency(loanProduct.isEnableInstallmentLevelDelinquency()); + final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueService.java index 15cf05d8a1c..6c7abcca34f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueService.java @@ -22,14 +22,14 @@ import java.time.LocalDate; import java.util.List; import java.util.stream.Collectors; +import lombok.NoArgsConstructor; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentPastDueData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; -import org.springframework.stereotype.Component; -@Component +@NoArgsConstructor public class LoanCalculateRepaymentPastDueService { public LoanRepaymentPastDueData retrieveLoanRepaymentPastDueAmountTillDate(Loan loan) { @@ -65,8 +65,7 @@ private List getPastDueRepayments(Loan loan) { List loanRepayments = loan.getRepaymentScheduleInstallments(); LocalDate currentBusinessDate = DateUtils.getBusinessLocalDate(); return loanRepayments.stream() - .filter((repayment) -> (!repayment.isObligationsMet() - && (repayment.getDueDate().isBefore(currentBusinessDate) || repayment.getDueDate().isEqual(currentBusinessDate)))) + .filter(repayment -> (!repayment.isObligationsMet() && !DateUtils.isAfter(repayment.getDueDate(), currentBusinessDate))) .collect(Collectors.toList()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java index 827c8d3ef2b..ebfbd5cd214 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java @@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; @@ -50,9 +51,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class LoanChargeAssembler { @@ -303,7 +302,7 @@ public LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefiniti if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && loan.isMultiDisburmentLoan()) { amountPercentageAppliedTo = BigDecimal.ZERO; for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) { - if (!loanDisbursementDetails.expectedDisbursementDate().isAfter(dueDate)) { + if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), dueDate)) { amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.principal()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java index 82591ec5ab7..976c04f2e31 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java @@ -23,26 +23,18 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class LoanChargePaidByReadPlatformServiceImpl implements LoanChargePaidByReadPlatformService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - @Autowired - public LoanChargePaidByReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - - } - @Override public List getLoanChargesPaidByTransactionId(Long transactionId) { this.context.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java index f61c454ff07..79d2e453298 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java @@ -51,9 +51,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index 83b4c050838..7bebdf28abf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -130,11 +130,9 @@ import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Slf4j -@Service @RequiredArgsConstructor public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatformService { @@ -166,13 +164,12 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) { - return e.getFromDate().isBefore(loanCharge.getDueDate()) && !loanCharge.getDueDate().isAfter(e.getDueDate()); + return DateUtils.isAfter(loanCharge.getDueDate(), e.getFromDate()) && !DateUtils.isAfter(loanCharge.getDueDate(), e.getDueDate()); } @Transactional @Override public CommandProcessingResult addLoanCharge(final Long loanId, final JsonCommand command) { - this.loanChargeApiJsonValidator.validateAddLoanCharge(command.json()); Loan loan = this.loanAssembler.assembleFrom(loanId); @@ -215,7 +212,7 @@ public CommandProcessingResult addLoanCharge(final Long loanId, final JsonComman validateAddLoanCharge(loan, chargeDefinition, loanCharge); addCharge(loan, chargeDefinition, loanCharge); isAppliedOnBackDate = true; - if (recalculateFrom.isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) { + if (DateUtils.isAfter(recalculateFrom, disbursementDetail.expectedDisbursementDateAsLocalDate())) { recalculateFrom = disbursementDetail.expectedDisbursementDateAsLocalDate(); } needToGenerateNewExternalId = true; @@ -233,7 +230,7 @@ public CommandProcessingResult addLoanCharge(final Long loanId, final JsonComman validateAddLoanCharge(loan, chargeDefinition, loanCharge); isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); - if (loanCharge.getDueLocalDate() == null || recalculateFrom.isAfter(loanCharge.getDueLocalDate())) { + if (loanCharge.getDueLocalDate() == null || DateUtils.isAfter(recalculateFrom, loanCharge.getDueLocalDate())) { isAppliedOnBackDate = true; recalculateFrom = loanCharge.getDueLocalDate(); } @@ -368,8 +365,7 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { throw new LoanChargeWaiveCannotBeReversedException( LoanChargeWaiveCannotBeReversedException.LoanChargeWaiveCannotUndoReason.LOAN_INACTIVE, loanCharge.getId()); } - if (loan.isChargedOff() && (loanTransaction.getTransactionDate().isBefore(loan.getChargedOffOnDate()) - || loanTransaction.getTransactionDate().isEqual(loan.getChargedOffOnDate()))) { + if (loan.isChargedOff() && !DateUtils.isAfter(loanTransaction.getTransactionDate(), loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Undo Loan transaction: " + loanTransaction.getId() + " is not allowed before or on the date when the loan got charged-off", @@ -745,17 +741,17 @@ public void applyOverdueChargesForLoan(final Long loanId, Collection installments = loan.getRepaymentScheduleInstallments(); LoanRepaymentScheduleInstallment lastInstallment = loan.fetchRepaymentScheduleInstallment(installments.size()); - if (lastChargeDate.isAfter(lastInstallment.getDueDate())) { + if (DateUtils.isAfter(lastChargeDate, lastInstallment.getDueDate())) { if (lastInstallment.isRecalculatedInterestComponent()) { installments.remove(lastInstallment); lastInstallment = loan.fetchRepaymentScheduleInstallment(installments.size()); 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 9ddf349e37c..349b5b999ac 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 @@ -141,12 +141,10 @@ import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @AllArgsConstructor -@Service @Transactional(readOnly = true) public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, LoanReadPlatformServiceCommon { @@ -645,7 +643,7 @@ public String loanSchema() { + " l.fixed_principal_percentage_per_installment fixedPrincipalPercentagePerInstallment, " + " l.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion," + " l.loan_status_id as lifeCycleStatusId, l.loan_transaction_strategy_code as transactionStrategyCode, " - + " l.loan_transaction_strategy_name as transactionStrategyName, " + + " l.loan_transaction_strategy_name as transactionStrategyName, l.enable_installment_level_delinquency as enableInstallmentLevelDelinquency, " + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc." + sqlGenerator.escape("name") + " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, " @@ -1045,6 +1043,7 @@ public LoanAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") fi final BigDecimal disbursedAmountPercentageForDownPayment = rs.getBigDecimal("disbursedAmountPercentageForDownPayment"); final boolean enableAutoRepaymentForDownPayment = rs.getBoolean("enableAutoRepaymentForDownPayment"); final boolean disableScheduleExtensionForDownPayment = rs.getBoolean("disableScheduleExtensionForDownPayment"); + final boolean enableInstallmentLevelDelinquency = rs.getBoolean("enableInstallmentLevelDelinquency"); return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, @@ -1062,7 +1061,7 @@ public LoanAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") fi canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud, lastClosedBusinessDate, overpaidOnDate, isChargedOff, enableDownPayment, disbursedAmountPercentageForDownPayment, - enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment); + enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment, enableInstallmentLevelDelinquency); } } @@ -1140,7 +1139,7 @@ public String schema() { + " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, " + " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, " + " ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff, ls.total_paid_in_advance_derived as totalPaidInAdvanceForPeriod, " - + " ls.total_paid_late_derived as totalPaidLateForPeriod, ls.credits_amount as totalCredits " + + " ls.total_paid_late_derived as totalPaidLateForPeriod, ls.credits_amount as totalCredits, ls.is_down_payment isDownPayment " + " from m_loan_repayment_schedule ls "; } @@ -1224,8 +1223,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.getPrincipal()); disbursementPeriodIds.add(data.getId()); } else if (data.isDueForDisbursement(fromDate, dueDate) && !disbursementPeriodIds.contains(data.getId())) { - if (!excludePastUndisbursed || data.isDisbursed() - || !data.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) { + if (!excludePastUndisbursed || data.isDisbursed() || !DateUtils.isBeforeBusinessDate(data.disbursementDate())) { principal = principal.add(data.getPrincipal()); LoanSchedulePeriodData periodData; if (data.getChargeAmount() == null) { @@ -1351,14 +1349,18 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(principalDue); } - final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, - dueDate, obligationsMetOnDate, complete, principalDue, principalPaid, principalWrittenOff, principalOutstanding, - outstandingPrincipalBalanceOfLoan, interestExpectedDue, interestPaid, interestWaived, interestWrittenOff, - interestOutstanding, feeChargesExpectedDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, - feeChargesOutstanding, penaltyChargesExpectedDue, penaltyChargesPaid, penaltyChargesWaived, - penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaidForPeriod, - totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, - totalOutstandingForPeriod, totalActualCostOfLoanForPeriod, totalInstallmentAmount, credits); + final boolean isDownPayment = rs.getBoolean("isDownPayment"); + + LoanSchedulePeriodData periodData; + + periodData = LoanSchedulePeriodData.periodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete, + principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, + interestExpectedDue, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesExpectedDue, + feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesExpectedDue, + penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, + totalPaidForPeriod, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaivedForPeriod, + totalWrittenOffForPeriod, totalOutstandingForPeriod, totalActualCostOfLoanForPeriod, totalInstallmentAmount, + credits, isDownPayment); periods.add(periodData); } @@ -2300,13 +2302,13 @@ public LoanSchedulePeriodData mapRow(ResultSet rs, @SuppressWarnings("unused") i final BigDecimal totalInstallmentAmount = null; final BigDecimal totalCredits = null; - return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete, + return LoanSchedulePeriodData.periodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding, - totalActualCostOfLoanForPeriod, totalInstallmentAmount, totalCredits); + totalActualCostOfLoanForPeriod, totalInstallmentAmount, totalCredits, false); } } @@ -2382,12 +2384,14 @@ public Collection retrieveLoanInterestRatePeriodData(Loa final Collection intRates = this.floatingRatesReadPlatformService .retrieveInterestRatePeriods(loanData.getLoanProductId()); for (final InterestRatePeriodData rate : intRates) { - if (loanData.getTimeline() != null && rate.getFromDate().compareTo(loanData.getTimeline().getDisbursementDate()) > 0 - && loanData.isFloatingInterestRate()) { + if (loanData.getTimeline() == null) { + continue; + } + boolean isAfterDisbursement = DateUtils.isAfter(rate.getFromDate(), loanData.getTimeline().getDisbursementDate()); + if (isAfterDisbursement && loanData.isFloatingInterestRate()) { updateInterestRatePeriodData(rate, loanData); intRatePeriodData.add(rate); - } else if (loanData.getTimeline() != null - && rate.getFromDate().compareTo(loanData.getTimeline().getDisbursementDate()) <= 0) { + } else if (!isAfterDisbursement) { updateInterestRatePeriodData(rate, loanData); intRatePeriodData.add(rate); break; @@ -2413,7 +2417,7 @@ private void updateInterestRatePeriodData(InterestRatePeriodData rate, LoanAccou } rate.setEffectiveInterestRate(effectiveInterestRate); - if (loan.getTimeline() != null && rate.getFromDate().compareTo(loan.getTimeline().getDisbursementDate()) < 0) { + if (loan.getTimeline() != null && DateUtils.isBefore(rate.getFromDate(), loan.getTimeline().getDisbursementDate())) { rate.setFromDate(loan.getTimeline().getDisbursementDate()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java index 241d608d1d5..5ca41a4f318 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java @@ -26,11 +26,9 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa; -import org.springframework.stereotype.Service; @Slf4j @RequiredArgsConstructor -@Service public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlatformService { private final BusinessEventNotifierService businessEventNotifierService; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java index 7c1450b569f..a247bc5ad7f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; @@ -59,10 +60,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -@Component +@RequiredArgsConstructor public class LoanUtilService { private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; @@ -75,23 +74,6 @@ public class LoanUtilService { private final FromJsonHelper fromApiJsonHelper; private final CalendarReadPlatformService calendarReadPlatformService; - @Autowired - public LoanUtilService(final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, - final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService, - final HolidayRepository holidayRepository, final WorkingDaysRepositoryWrapper workingDaysRepository, - final LoanScheduleGeneratorFactory loanScheduleFactory, final FloatingRatesReadPlatformService floatingRatesReadPlatformService, - final FromJsonHelper fromApiJsonHelper, final CalendarReadPlatformService calendarReadPlatformService) { - this.applicationCurrencyRepository = applicationCurrencyRepository; - this.calendarInstanceRepository = calendarInstanceRepository; - this.configurationDomainService = configurationDomainService; - this.holidayRepository = holidayRepository; - this.workingDaysRepository = workingDaysRepository; - this.loanScheduleFactory = loanScheduleFactory; - this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; - this.fromApiJsonHelper = fromApiJsonHelper; - this.calendarReadPlatformService = calendarReadPlatformService; - } - public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) { final HolidayDetailDTO holidayDetailDTO = null; return buildScheduleGeneratorDTO(loan, recalculateFrom, holidayDetailDTO); @@ -299,7 +281,7 @@ private LocalDate generateCalculatedRepaymentStartDate(final CalendarHistoryData isSkipRepaymentOnFirstMonth = isLoanRepaymentsSyncWithMeeting(loan.group(), historyList.get(0).getCalendar()); } calculatedRepaymentsStartingFromDate = CalendarUtils.getNextRepaymentMeetingDate(historyList.get(0).getRecurrence(), - historyList.get(0).getStartDateLocalDate(), actualDisbursementDate, repayEvery, frequency, workingDays, + historyList.get(0).getStartDate(), actualDisbursementDate, repayEvery, frequency, workingDays, isSkipRepaymentOnFirstMonth, numberOfDays); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index e72be1ab3bd..9e72ff81b8c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -40,6 +40,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.batch.exception.ErrorHandler; import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException; import org.apache.fineract.cob.service.LoanAccountLockService; import org.apache.fineract.infrastructure.codes.domain.CodeValue; @@ -81,6 +82,8 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionDownPaymentPostBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionDownPaymentPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoChargeOffBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent; @@ -193,12 +196,9 @@ import org.apache.fineract.portfolio.transfer.api.TransferApiConstants; import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service @Slf4j @RequiredArgsConstructor public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService { @@ -247,7 +247,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final ExternalIdFactory externalIdFactory; private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService; private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; - private final JdbcTemplate jdbcTemplate; + private final ErrorHandler errorHandler; @Transactional @Override @@ -291,7 +291,7 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); - if (loan.isChargedOff() && actualDisbursementDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(actualDisbursementDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loanId + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -405,7 +405,7 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand "Loan to be closed with this topup is not active."); } final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate(); - if (loan.getDisbursementDate().isBefore(lastUserTransactionOnLoanToClose)) { + if (DateUtils.isBefore(loan.getDisbursementDate(), lastUserTransactionOnLoanToClose)) { throw new GeneralPlatformDomainRuleException( "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed", "Disbursal date of this loan application " + loan.getDisbursementDate() @@ -436,6 +436,7 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand actualDisbursementDate, txnExternalId); disbursementTransaction.updateLoan(loan); loan.addLoanTransaction(disbursementTransaction); + LoanTransaction savedLoanTransaction = loanTransactionRepository.saveAndFlush(disbursementTransaction); } if (loan.getRepaymentScheduleInstallments().isEmpty()) { /* @@ -456,7 +457,14 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null, downPaymentEnabled); } loan.adjustNetDisbursalAmount(amountToDisburse.getAmount()); - loan.handleDownPayment(amountToDisburse.getAmount(), command, scheduleGeneratorDTO); + if (loan.isAutoRepaymentForDownPaymentEnabled()) { + businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionDownPaymentPreBusinessEvent(loan)); + LoanTransaction downPaymentTransaction = loan.handleDownPayment(amountToDisburse.getAmount(), command, + scheduleGeneratorDTO); + LoanTransaction savedLoanTransaction = loanTransactionRepository.saveAndFlush(downPaymentTransaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionDownPaymentPostBusinessEvent(savedLoanTransaction)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + } } if (!changes.isEmpty()) { if (changedTransactionDetail != null) { @@ -1123,7 +1131,7 @@ public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Lo LocalDate recalculateFrom = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - recalculateFrom = transactionToAdjust.getTransactionDate().isAfter(transactionDate) ? transactionDate + recalculateFrom = DateUtils.isAfter(transactionToAdjust.getTransactionDate(), transactionDate) ? transactionDate : transactionToAdjust.getTransactionDate(); } @@ -1141,11 +1149,11 @@ public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Lo this.loanTransactionRepository.saveAndFlush(newTransactionDetail); } - /*** + /* * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the * time being, not a major issue for now as this loop is entered only in edge cases (when a adjustment is made * before the latest payment recorded against the loan) - ***/ + */ if (changedTransactionDetail != null) { for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); @@ -1438,7 +1446,7 @@ public CommandProcessingResult writeOff(final Long loanId, final JsonCommand com } checkClientOrGroupActive(loan); - if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loanId + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -1504,7 +1512,7 @@ public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand co final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); - if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) { + if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) { throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " + loanId + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", @@ -2426,13 +2434,8 @@ public CommandProcessingResult recoverFromGuarantor(final Long loanId) { @SuppressWarnings("unused") private void fallbackRecalculateInterest(Throwable t) { // NOTE: allow caller to catch the exceptions - - if (t instanceof RuntimeException re) { - throw re; - } - // NOTE: wrap throwable only if really necessary - throw new RuntimeException(t); + throw errorHandler.getMappable(t, null, null, "loan.recalculateinterest"); } @Override @@ -2595,8 +2598,7 @@ public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) private void checkIfLoanIsPaidInAdvance(final Long loanId, final BigDecimal transactionAmount) { BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId).getPaidInAdvance(); - if (overpaid == null || overpaid.compareTo(BigDecimal.ZERO) == 0 ? Boolean.TRUE - : Boolean.FALSE || transactionAmount.floatValue() > overpaid.floatValue()) { + if (overpaid == null || overpaid.compareTo(BigDecimal.ZERO) == 0 || transactionAmount.floatValue() > overpaid.floatValue()) { if (overpaid == null) { overpaid = BigDecimal.ZERO; } @@ -2682,7 +2684,7 @@ public CommandProcessingResult forecloseLoan(final Long loanId, final JsonComman String noteText = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParamName, element); LoanRescheduleRequest loanRescheduleRequest = null; for (LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) { - if (!loanDisbursementDetails.expectedDisbursementDateAsLocalDate().isAfter(transactionDate) + if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDateAsLocalDate(), transactionDate) && loanDisbursementDetails.actualDisbursementDate() == null) { final String defaultUserMessage = "The loan with undisbursed tranche before foreclosure cannot be foreclosed."; throw new LoanForeclosureException("loan.with.undisbursed.tranche.before.foreclosure.cannot.be.foreclosured", @@ -2729,12 +2731,12 @@ public CommandProcessingResult chargeOff(JsonCommand command) { throw new GeneralPlatformDomainRuleException("error.msg.loan.is.already.charged.off", "Loan: " + loanId + " is already charged-off", loanId); } - if (transactionDate.isBefore(loan.getLastUserTransactionDate())) { + if (DateUtils.isBefore(transactionDate, loan.getLastUserTransactionDate())) { throw new GeneralPlatformDomainRuleException("error.msg.loan.charge.off.is.before.than.the.last.user.transaction", "Loan: " + loanId + " charge-off cannot be executed. User transaction was found after the charge-off transaction date!", loanId); } - if (transactionDate.isAfter(DateUtils.getBusinessLocalDate())) { + if (DateUtils.isDateInTheFuture(transactionDate)) { final String errorMessage = "The transaction date cannot be in the future."; throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.cannot.be.a.future.date", errorMessage, transactionDate); @@ -2845,27 +2847,23 @@ private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loa final String errorMessage = "tranches.should.be.disbursed.more.than.one.to.undo.last.disbursal"; throw new LoanMultiDisbursementException(errorMessage); } - } private void syncExpectedDateWithActualDisbursementDate(final Loan loan, LocalDate actualDisbursementDate) { if (!loan.getExpectedDisbursedOnLocalDate().equals(actualDisbursementDate)) { throw new DateMismatchException(actualDisbursementDate, loan.getExpectedDisbursedOnLocalDate()); } - } private void validateTransactionsForTransfer(final Loan loan, final LocalDate transferDate) { - for (LoanTransaction transaction : loan.getLoanTransactions()) { - if ((transaction.getTransactionDate().isEqual(transferDate) && transaction.getSubmittedOnDate().isEqual(transferDate)) - || transaction.getTransactionDate().isAfter(transferDate)) { + if ((DateUtils.isEqual(transferDate, transaction.getTransactionDate()) + && DateUtils.isEqual(transferDate, transaction.getSubmittedOnDate())) + || DateUtils.isBefore(transferDate, transaction.getTransactionDate())) { throw new GeneralPlatformDomainRuleException(TransferApiConstants.transferClientLoanException, TransferApiConstants.transferClientLoanExceptionMessage, transaction.getCreatedDateTime().toLocalDate(), transferDate); } - } - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java index 6d0846fefca..396af75ee2a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java @@ -22,18 +22,14 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; -@Component -@Scope("prototype") +@NoArgsConstructor +@Slf4j public class RecalculateInterestPoster implements Callable { - private static final Logger LOG = LoggerFactory.getLogger(RecalculateInterestPoster.class); - private Collection loanIds; private LoanWritePlatformService loanWritePlatformService; @@ -50,7 +46,7 @@ public Void call() throws JobExecutionException { if (!loanIds.isEmpty()) { List errors = new ArrayList<>(); for (Long loanId : loanIds) { - LOG.debug("Loan ID {}", loanId); + log.debug("Loan ID {}", loanId); try { loanWritePlatformService.recalculateInterest(loanId); } catch (Exception e) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java index 1359e00e6b6..6ac3ca3d181 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java @@ -26,9 +26,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class ReplayedTransactionBusinessEventServiceImpl implements ReplayedTransactionBusinessEventService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java new file mode 100644 index 00000000000..cc8069292e8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -0,0 +1,433 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.batch.exception.ErrorHandler; +import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper; +import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepositoryWrapper; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService; +import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityRelationRepository; +import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityToEntityMappingRepository; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.holiday.domain.HolidayRepository; +import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.apache.fineract.organisation.office.domain.OfficeRepository; +import org.apache.fineract.organisation.staff.domain.StaffRepository; +import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; +import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator; +import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository; +import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; +import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; +import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; +import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; +import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService; +import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; +import org.apache.fineract.portfolio.calendar.domain.CalendarRepository; +import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; +import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.apache.fineract.portfolio.charge.service.ChargeDropdownReadPlatformService; +import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; +import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; +import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler; +import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService; +import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; +import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService; +import org.apache.fineract.portfolio.fund.domain.FundRepository; +import org.apache.fineract.portfolio.fund.service.FundReadPlatformService; +import org.apache.fineract.portfolio.group.domain.GroupRepository; +import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; +import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetailsRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; +import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; +import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; +import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionRelationMapper; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; +import org.apache.fineract.portfolio.loanaccount.service.BulkLoansReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.BulkLoansReadPlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService; +import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; +import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanStatusChangePlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanStatusChangePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; +import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.loanaccount.service.RecalculateInterestPoster; +import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService; +import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventServiceImpl; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator; +import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; +import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService; +import org.apache.fineract.portfolio.rate.service.RateAssembler; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; +import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +@Configuration +public class LoanAccountConfiguration { + + @Bean + @ConditionalOnMissingBean(BulkLoansReadPlatformService.class) + public BulkLoansReadPlatformService bulkLoansReadPlatformServicev(JdbcTemplate jdbcTemplate, PlatformSecurityContext context, + AccountDetailsReadPlatformService accountDetailsReadPlatformService) { + return new BulkLoansReadPlatformServiceImpl(jdbcTemplate, context, accountDetailsReadPlatformService); + } + + @Bean + @ConditionalOnMissingBean(GLIMAccountInfoReadPlatformService.class) + public GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context, + AccountDetailsReadPlatformService accountDetailsReadPlatforService) { + return new GLIMAccountInfoReadPlatformServiceImpl(jdbcTemplate, context, accountDetailsReadPlatforService); + } + + @Bean + @ConditionalOnMissingBean(GLIMAccountInfoWritePlatformService.class) + public GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService(PlatformSecurityContext context, + GLIMAccountInfoRepository glimAccountRepository, + + LoanRepository loanRepository) { + return new GLIMAccountInfoWritePlatformServiceImpl(context, glimAccountRepository, loanRepository); + } + + @Bean + @ConditionalOnMissingBean(LoanAccrualPlatformService.class) + public LoanAccrualPlatformService loanAccrualPlatformService(LoanReadPlatformService loanReadPlatformService, + LoanAccrualWritePlatformService loanAccrualWritePlatformService) { + return new LoanAccrualPlatformServiceImpl(loanReadPlatformService, loanAccrualWritePlatformService); + } + + @Bean + @ConditionalOnMissingBean(LoanAccrualTransactionBusinessEventService.class) + public LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService( + + BusinessEventNotifierService businessEventNotifierService) { + return new LoanAccrualTransactionBusinessEventServiceImpl(businessEventNotifierService); + } + + @Bean + @ConditionalOnMissingBean(LoanAccrualWritePlatformService.class) + public LoanAccrualWritePlatformService loanAccrualWritePlatformService(LoanReadPlatformService loanReadPlatformService, + LoanChargeReadPlatformService loanChargeReadPlatformService, JdbcTemplate jdbcTemplate, + DatabaseSpecificSQLGenerator sqlGenerator, JournalEntryWritePlatformService journalEntryWritePlatformService, + PlatformSecurityContext context, LoanRepositoryWrapper loanRepositoryWrapper, LoanRepository loanRepository, + OfficeRepository officeRepository, BusinessEventNotifierService businessEventNotifierService, + LoanTransactionRepository loanTransactionRepository, + LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, + ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory) { + return new LoanAccrualWritePlatformServiceImpl(loanReadPlatformService, loanChargeReadPlatformService, jdbcTemplate, + journalEntryWritePlatformService, context, loanRepositoryWrapper, loanRepository, officeRepository, + businessEventNotifierService, loanTransactionRepository, loanAccrualTransactionBusinessEventService, + configurationDomainService, externalIdFactory); + } + + @Bean + @ConditionalOnMissingBean(LoanApplicationWritePlatformService.class) + public LoanApplicationWritePlatformService loanApplicationWritePlatformService(PlatformSecurityContext context, + FromJsonHelper fromJsonHelper, LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator, + LoanProductDataValidator loanProductCommandFromApiJsonDeserializer, + LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper, + NoteRepository noteRepository, LoanScheduleCalculationPlatformService calculationPlatformService, LoanAssembler loanAssembler, + ClientRepositoryWrapper clientRepository, LoanProductRepository loanProductRepository, LoanChargeAssembler loanChargeAssembler, + LoanCollateralAssembler loanCollateralAssembler, AprCalculator aprCalculator, AccountNumberGenerator accountNumberGenerator, + LoanSummaryWrapper loanSummaryWrapper, GroupRepositoryWrapper groupRepository, + LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + CalendarRepository calendarRepository, CalendarInstanceRepository calendarInstanceRepository, + SavingsAccountAssembler savingsAccountAssembler, AccountAssociationsRepository accountAssociationsRepository, + LoanReadPlatformService loanReadPlatformService, AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, + BusinessEventNotifierService businessEventNotifierService, ConfigurationDomainService configurationDomainService, + LoanScheduleAssembler loanScheduleAssembler, LoanUtilService loanUtilService, + CalendarReadPlatformService calendarReadPlatformService, + EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, + GlobalConfigurationRepositoryWrapper globalConfigurationRepository, + FineractEntityToEntityMappingRepository entityMappingRepository, + FineractEntityRelationRepository fineractEntityRelationRepository, + LoanProductReadPlatformService loanProductReadPlatformService, + + RateAssembler rateAssembler, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService, + GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService, + LoanLifecycleStateMachine defaultLoanLifecycleStateMachine) { + return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, fromJsonHelper, loanApplicationTransitionApiJsonValidator, + loanProductCommandFromApiJsonDeserializer, fromApiJsonDeserializer, loanRepositoryWrapper, noteRepository, + calculationPlatformService, loanAssembler, clientRepository, loanProductRepository, loanChargeAssembler, + loanCollateralAssembler, aprCalculator, accountNumberGenerator, loanSummaryWrapper, groupRepository, + loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountAssembler, + accountAssociationsRepository, loanReadPlatformService, accountNumberFormatRepository, businessEventNotifierService, + configurationDomainService, loanScheduleAssembler, loanUtilService, calendarReadPlatformService, + entityDatatableChecksWritePlatformService, globalConfigurationRepository, entityMappingRepository, + fineractEntityRelationRepository, loanProductReadPlatformService, rateAssembler, glimAccountInfoWritePlatformService, + glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine); + } + + @Bean + @ConditionalOnMissingBean(LoanArrearsAgingService.class) + public LoanArrearsAgingService loanArrearsAgingService(JdbcTemplate jdbcTemplate, + BusinessEventNotifierService businessEventNotifierService, DatabaseSpecificSQLGenerator sqlGenerator) { + return new LoanArrearsAgingServiceImpl(jdbcTemplate, businessEventNotifierService, sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(LoanAssembler.class) + public LoanAssembler loanAssembler(FromJsonHelper fromApiJsonHelper, LoanRepositoryWrapper loanRepository, + LoanProductRepository loanProductRepository, ClientRepositoryWrapper clientRepository, GroupRepository groupRepository, + FundRepository fundRepository, StaffRepository staffRepository, CodeValueRepositoryWrapper codeValueRepository, + LoanScheduleAssembler loanScheduleAssembler, LoanChargeAssembler loanChargeAssembler, + LoanCollateralAssembler collateralAssembler, LoanSummaryWrapper loanSummaryWrapper, + LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + HolidayRepository holidayRepository, ConfigurationDomainService configurationDomainService, + WorkingDaysRepositoryWrapper workingDaysRepository, LoanUtilService loanUtilService, RateAssembler rateAssembler, + LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, ExternalIdFactory externalIdFactory) { + return new LoanAssembler(fromApiJsonHelper, loanRepository, loanProductRepository, clientRepository, groupRepository, + fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler, + loanSummaryWrapper, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService, + workingDaysRepository, loanUtilService, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory); + } + + @Bean + @ConditionalOnMissingBean(LoanCalculateRepaymentPastDueService.class) + public LoanCalculateRepaymentPastDueService loanCalculateRepaymentPastDueService() { + return new LoanCalculateRepaymentPastDueService(); + } + + @Bean + @ConditionalOnMissingBean(LoanChargeAssembler.class) + public LoanChargeAssembler loanChargeAssembler( + + FromJsonHelper fromApiJsonHelper, ChargeRepositoryWrapper chargeRepository, LoanChargeRepository loanChargeRepository, + LoanProductRepository loanProductRepository, ExternalIdFactory externalIdFactory) { + return new LoanChargeAssembler(fromApiJsonHelper, chargeRepository, loanChargeRepository, loanProductRepository, externalIdFactory); + } + + @Bean + @ConditionalOnMissingBean(LoanChargePaidByReadPlatformService.class) + public LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService(JdbcTemplate jdbcTemplate, + PlatformSecurityContext context) { + return new LoanChargePaidByReadPlatformServiceImpl(jdbcTemplate, context); + } + + @Bean + @ConditionalOnMissingBean(LoanChargeReadPlatformService.class) + public LoanChargeReadPlatformService loanChargeReadPlatformService(JdbcTemplate jdbcTemplate, + ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, DropdownReadPlatformService dropdownReadPlatformService, + LoanChargeRepository loanChargeRepository) { + return new LoanChargeReadPlatformServiceImpl(jdbcTemplate, chargeDropdownReadPlatformService, dropdownReadPlatformService, + loanChargeRepository); + } + + @Bean + @ConditionalOnMissingBean(LoanChargeWritePlatformService.class) + public LoanChargeWritePlatformService loanChargeWritePlatformService(LoanChargeApiJsonValidator loanChargeApiJsonValidator, + LoanAssembler loanAssembler, ChargeRepositoryWrapper chargeRepository, + BusinessEventNotifierService businessEventNotifierService, LoanTransactionRepository loanTransactionRepository, + AccountTransfersWritePlatformService accountTransfersWritePlatformService, LoanRepositoryWrapper loanRepositoryWrapper, + JournalEntryWritePlatformService journalEntryWritePlatformService, LoanAccountDomainService loanAccountDomainService, + LoanChargeRepository loanChargeRepository, LoanWritePlatformService loanWritePlatformService, LoanUtilService loanUtilService, + LoanChargeReadPlatformService loanChargeReadPlatformService, LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, + AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, FromJsonHelper fromApiJsonHelper, + ConfigurationDomainService configurationDomainService, + LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + ExternalIdFactory externalIdFactory, AccountTransferDetailRepository accountTransferDetailRepository, + LoanChargeAssembler loanChargeAssembler, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService, + PaymentDetailWritePlatformService paymentDetailWritePlatformService, NoteRepository noteRepository, + LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService + + ) { + return new LoanChargeWritePlatformServiceImpl(loanChargeApiJsonValidator, loanAssembler, chargeRepository, + businessEventNotifierService, loanTransactionRepository, accountTransfersWritePlatformService, loanRepositoryWrapper, + journalEntryWritePlatformService, loanAccountDomainService, loanChargeRepository, loanWritePlatformService, loanUtilService, + loanChargeReadPlatformService, defaultLoanLifecycleStateMachine, accountAssociationsReadPlatformService, fromApiJsonHelper, + configurationDomainService, loanRepaymentScheduleTransactionProcessorFactory, externalIdFactory, + accountTransferDetailRepository, loanChargeAssembler, replayedTransactionBusinessEventService, + paymentDetailWritePlatformService, noteRepository, loanAccrualTransactionBusinessEventService); + } + + @Bean + @ConditionalOnMissingBean(LoanReadPlatformService.class) + public LoanReadPlatformServiceImpl loanReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context, + LoanRepositoryWrapper loanRepositoryWrapper, ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, + LoanProductReadPlatformService loanProductReadPlatformService, ClientReadPlatformService clientReadPlatformService, + GroupReadPlatformService groupReadPlatformService, LoanDropdownReadPlatformService loanDropdownReadPlatformService, + FundReadPlatformService fundReadPlatformService, ChargeReadPlatformService chargeReadPlatformService, + CodeValueReadPlatformService codeValueReadPlatformService, CalendarReadPlatformService calendarReadPlatformService, + StaffReadPlatformService staffReadPlatformService, PaginationHelper paginationHelper, + NamedParameterJdbcTemplate namedParameterJdbcTemplate, PaymentTypeReadPlatformService paymentTypeReadPlatformService, + LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + FloatingRatesReadPlatformService floatingRatesReadPlatformService, LoanUtilService loanUtilService, + ConfigurationDomainService configurationDomainService, AccountDetailsReadPlatformService accountDetailsReadPlatformService, + ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, + DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository, + LoanTransactionRelationRepository loanTransactionRelationRepository, + LoanTransactionRelationMapper loanTransactionRelationMapper, + LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService) { + return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository, + loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService, + fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService, + staffReadPlatformService, paginationHelper, namedParameterJdbcTemplate, paymentTypeReadPlatformService, + loanRepaymentScheduleTransactionProcessorFactory, floatingRatesReadPlatformService, loanUtilService, + configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator, + delinquencyReadPlatformService, loanTransactionRepository, loanTransactionRelationRepository, loanTransactionRelationMapper, + loanChargePaidByReadPlatformService + + ); + } + + @Bean + @ConditionalOnMissingBean(LoanStatusChangePlatformService.class) + public LoanStatusChangePlatformService loanStatusChangePlatformService(BusinessEventNotifierService businessEventNotifierService, + LoanAccountDomainServiceJpa loanAccountDomainService) { + return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccountDomainService); + } + + @Bean + @ConditionalOnMissingBean(LoanUtilService.class) + public LoanUtilService loanUtilService(ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, + CalendarInstanceRepository calendarInstanceRepository, ConfigurationDomainService configurationDomainService, + HolidayRepository holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository, + LoanScheduleGeneratorFactory loanScheduleFactory, FloatingRatesReadPlatformService floatingRatesReadPlatformService, + FromJsonHelper fromApiJsonHelper, CalendarReadPlatformService calendarReadPlatformService) { + return new LoanUtilService(applicationCurrencyRepository, calendarInstanceRepository, configurationDomainService, holidayRepository, + workingDaysRepository, loanScheduleFactory, floatingRatesReadPlatformService, fromApiJsonHelper, + calendarReadPlatformService); + } + + @Bean + @ConditionalOnMissingBean(LoanWritePlatformService.class) + public LoanWritePlatformService loanWritePlatformService(PlatformSecurityContext context, + LoanEventApiJsonValidator loanEventApiJsonValidator, + LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper, + LoanAccountDomainService loanAccountDomainService, NoteRepository noteRepository, + LoanTransactionRepository loanTransactionRepository, LoanTransactionRelationRepository loanTransactionRelationRepository, + LoanAssembler loanAssembler, JournalEntryWritePlatformService journalEntryWritePlatformService, + CalendarInstanceRepository calendarInstanceRepository, PaymentDetailWritePlatformService paymentDetailWritePlatformService, + HolidayRepositoryWrapper holidayRepository, ConfigurationDomainService configurationDomainService, + WorkingDaysRepositoryWrapper workingDaysRepository, AccountTransfersWritePlatformService accountTransfersWritePlatformService, + AccountTransfersReadPlatformService accountTransfersReadPlatformService, + AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, LoanReadPlatformService loanReadPlatformService, + FromJsonHelper fromApiJsonHelper, CalendarRepository calendarRepository, + LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, + LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper, + AccountAssociationsRepository accountAssociationRepository, AccountTransferDetailRepository accountTransferDetailRepository, + BusinessEventNotifierService businessEventNotifierService, GuarantorDomainService guarantorDomainService, + LoanUtilService loanUtilService, LoanSummaryWrapper loanSummaryWrapper, + EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, + LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, CodeValueRepositoryWrapper codeValueRepository, + CashierTransactionDataValidator cashierTransactionDataValidator, GLIMAccountInfoRepository glimRepository, + LoanRepository loanRepository, RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler, + PostDatedChecksRepository postDatedChecksRepository, LoanDisbursementDetailsRepository loanDisbursementDetailsRepository, + LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository, + LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService, + ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService, + LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, ErrorHandler errorHandler) { + return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanEventApiJsonValidator, loanUpdateCommandFromApiJsonDeserializer, + loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository, + loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository, + paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository, + accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService, + loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService, + loanApplicationCommandFromApiJsonHelper, accountAssociationRepository, accountTransferDetailRepository, + businessEventNotifierService, guarantorDomainService, loanUtilService, loanSummaryWrapper, + entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository, + cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler, + postDatedChecksRepository, loanDisbursementDetailsRepository, loanRepaymentScheduleInstallmentRepository, + defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService, + loanAccrualTransactionBusinessEventService, errorHandler); + } + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean(RecalculateInterestPoster.class) + public RecalculateInterestPoster recalculateInterestPoster() { + return new RecalculateInterestPoster(); + } + + @Bean + @ConditionalOnMissingBean(ReplayedTransactionBusinessEventService.class) + public ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService( + BusinessEventNotifierService businessEventNotifierService, LoanTransactionRepository loanTransactionRepository) { + return new ReplayedTransactionBusinessEventServiceImpl(businessEventNotifierService, loanTransactionRepository); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java index 4f891f80e25..cd9a28ed290 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java @@ -102,7 +102,7 @@ private PostLoanProductsRequest() {} @Schema(example = "false") public Boolean useBorrowerCycle; @Schema(example = "2") - public Integer repaymentFrequencyType; + public Long repaymentFrequencyType; @Schema(example = "2") public Integer interestRateFrequencyType; @Schema(example = "1") @@ -152,6 +152,8 @@ private PostLoanProductsRequest() {} public Boolean holdGuaranteeFunds; @Schema(example = "1") public Long delinquencyBucketId; + @Schema(example = "false") + public Boolean enableInstallmentLevelDelinquency; @Schema(example = "3") public Integer dueDaysForRepaymentEvent; @Schema(example = "3") @@ -1237,6 +1239,8 @@ private GetLoanCharge() {} @Schema(example = "50") public Integer principalThresholdForLastInstalment; public GetDelinquencyBucketsResponse delinquencyBucket; + @Schema(example = "false") + public Boolean enableInstallmentLevelDelinquency; @Schema(example = "true") public Boolean disallowExpectedDisbursements; @Schema(example = "3") @@ -1347,9 +1351,9 @@ private PutLoanProductsProductIdRequest() {} @Schema(example = "50") public Integer overAppliedNumber; @Schema(example = "1") - public Integer daysInMonthType; + public Long daysInMonthType; @Schema(example = "1") - public Integer daysInYearType; + public Long daysInYearType; @Schema(example = "true") public Boolean allowPartialPeriodInterestCalcualtion; @Schema(example = "179") @@ -1372,6 +1376,8 @@ private PutLoanProductsProductIdRequest() {} public Boolean holdGuaranteeFunds; @Schema(example = "1") public Long delinquencyBucketId; + @Schema(example = "false") + public Boolean enableInstallmentLevelDelinquency; @Schema(example = "3") public Integer dueDaysForRepaymentEvent; @Schema(example = "3") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java index 6a2f235c3d4..49b98021a9c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java @@ -45,9 +45,12 @@ import org.apache.fineract.portfolio.fund.data.FundData; import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; +import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; @@ -204,6 +207,7 @@ public class LoanProductData implements Serializable { private final BigDecimal disbursedAmountPercentageForDownPayment; private final boolean enableAutoRepaymentForDownPayment; private final boolean disableScheduleExtensionForDownPayment; + private final boolean enableInstallmentLevelDelinquency; /** * Used when returning lookup information about loan product for dropdowns. @@ -299,6 +303,7 @@ public static LoanProductData lookup(final Long id, final String name, final Boo final boolean enableAutoRepaymentForDownPayment = false; final EnumOptionData repaymentStartDateType = null; final boolean disableScheduleExtensionForDownPayment = false; + final boolean enableInstallmentLevelDelinquency = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -318,7 +323,7 @@ public static LoanProductData lookup(final Long id, final String name, final Boo syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, - paymentAllocation, repaymentStartDateType, disableScheduleExtensionForDownPayment); + paymentAllocation, repaymentStartDateType, disableScheduleExtensionForDownPayment, enableInstallmentLevelDelinquency); } @@ -414,6 +419,7 @@ public static LoanProductData lookupWithCurrency(final Long id, final String nam final Collection paymentAllocation = null; final EnumOptionData repaymentStartDateType = null; final boolean disableScheduleExtensionForDownPayment = false; + final boolean enableInstallmentLevelDelinquency = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -433,7 +439,7 @@ public static LoanProductData lookupWithCurrency(final Long id, final String nam syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, - paymentAllocation, repaymentStartDateType, disableScheduleExtensionForDownPayment); + paymentAllocation, repaymentStartDateType, disableScheduleExtensionForDownPayment, enableInstallmentLevelDelinquency); } @@ -536,6 +542,7 @@ public static LoanProductData sensibleDefaultsForNewLoanProductCreation() { final Collection paymentAllocation = null; final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE); final boolean disableScheduleExtensionForDownPayment = false; + final boolean enableInstallmentLevelDelinquency = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -555,7 +562,7 @@ public static LoanProductData sensibleDefaultsForNewLoanProductCreation() { syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, - paymentAllocation, repaymentStartDateType, disableScheduleExtensionForDownPayment); + paymentAllocation, repaymentStartDateType, disableScheduleExtensionForDownPayment, enableInstallmentLevelDelinquency); } @@ -652,6 +659,7 @@ public static LoanProductData loanProductWithFloatingRates(final Long id, final final Collection paymentAllocation = null; final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE); final boolean disableScheduleExtensionForDownPayment = false; + final boolean enableInstallmentLevelDelinquency = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -671,7 +679,7 @@ public static LoanProductData loanProductWithFloatingRates(final Long id, final syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, - paymentAllocation, repaymentStartDateType, disableScheduleExtensionForDownPayment); + paymentAllocation, repaymentStartDateType, disableScheduleExtensionForDownPayment, enableInstallmentLevelDelinquency); } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings, @@ -720,7 +728,7 @@ public LoanProductData(final Long id, final String name, final String shortName, final Integer overDueDaysForRepaymentEvent, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final Collection paymentAllocation, final EnumOptionData repaymentStartDateType, - final boolean disableScheduleExtensionForDownPayment) { + final boolean disableScheduleExtensionForDownPayment, final boolean enableInstallmentLevelDelinquency) { this.id = id; this.name = name; this.shortName = shortName; @@ -844,10 +852,11 @@ public LoanProductData(final Long id, final String name, final String shortName, this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment; this.repaymentStartDateType = repaymentStartDateType; this.repaymentStartDateTypeOptions = null; - this.advancedPaymentAllocationTransactionTypes = null; - this.advancedPaymentAllocationFutureInstallmentAllocationRules = null; - this.advancedPaymentAllocationTypes = null; + this.advancedPaymentAllocationTransactionTypes = PaymentAllocationTransactionType.getValuesAsEnumOptionDataList(); + this.advancedPaymentAllocationFutureInstallmentAllocationRules = FutureInstallmentAllocationRule.getValuesAsEnumOptionDataList(); + this.advancedPaymentAllocationTypes = PaymentAllocationType.getValuesAsEnumOptionDataList(); this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment; + this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; } public LoanProductData(final LoanProductData productData, final Collection chargeOptions, @@ -1012,6 +1021,7 @@ public LoanProductData(final LoanProductData productData, final Collection nullIfEmpty(final Collection charges) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 7eb934dd973..51c4558564a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; @@ -161,7 +162,8 @@ public final class LoanProductDataValidator { LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.ENABLE_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.REPAYMENT_START_DATE_TYPE, - LoanProductConstants.DISABLE_SCHEDULE_EXTENSION_FOR_DOWN_PAYMENT)); + LoanProductConstants.DISABLE_SCHEDULE_EXTENSION_FOR_DOWN_PAYMENT, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, + LoanProductConstants.LOAN_SCHEDULE_TYPE)); private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName, LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName, @@ -762,6 +764,17 @@ public void validateForCreate(final String json) { .isOneOfTheseValues(1, 2); } + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element)) { + final Boolean enableInstallmentLevelDelinquency = this.fromApiJsonHelper + .extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element); + baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY) + .value(enableInstallmentLevelDelinquency).ignoreIfNull().validateForBooleanValue(); + } + + final String loanScheduleType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element); + baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_TYPE).value(loanScheduleType).ignoreIfNull() + .isOneOfEnumValues(LoanScheduleType.class); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -1731,6 +1744,13 @@ public void validateForUpdate(final String json, final LoanProduct loanProduct) baseDataValidator.reset().parameter(LoanProductConstants.REPAYMENT_START_DATE_TYPE).value(repaymentStartDateType).notNull() .isOneOfTheseValues(1, 2); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element)) { + final Boolean enableInstallmentLevelDelinquency = this.fromApiJsonHelper + .extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element); + baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY) + .value(enableInstallmentLevelDelinquency).ignoreIfNull().validateForBooleanValue(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -2267,7 +2287,9 @@ private void validatePartialPeriodSupport(final Integer interestCalculationPerio considerPartialPeriodUpdates = considerPartialPeriods; } } else if (loanProduct != null) { - considerPartialPeriodUpdates = loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion(); + if (!interestCalculationPeriodMethod.isDaily()) { + considerPartialPeriodUpdates = loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion(); + } } if (!considerPartialPeriodUpdates) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java index a1fa9f1926d..5dc8f59e64a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java @@ -54,9 +54,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index a4f990b2732..f4c20667b6a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -60,9 +60,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatformService { @@ -230,7 +228,7 @@ public String loanProductSchema() { + "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, " + "lp.can_define_fixed_emi_amount as canDefineInstallmentAmount, lp.instalment_amount_in_multiples_of as installmentAmountInMultiplesOf, " + "lp.due_days_for_repayment_event as dueDaysForRepaymentEvent, lp.overdue_days_for_repayment_event as overDueDaysForRepaymentEvent, lp.enable_down_payment as enableDownPayment, lp.disbursed_amount_percentage_for_down_payment as disbursedAmountPercentageForDownPayment, lp.enable_auto_repayment_for_down_payment as enableAutoRepaymentForDownPayment, lp.repayment_start_date_type_enum as repaymentStartDateType, " - + "lp.disable_schedule_extension_for_down_payment as disableScheduleExtensionForDownPayment, " + + "lp.disable_schedule_extension_for_down_payment as disableScheduleExtensionForDownPayment, lp.enable_installment_level_delinquency as enableInstallmentLevelDelinquency, " + "lpr.pre_close_interest_calculation_strategy as preCloseInterestCalculationStrategy, " + "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, " + "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, " @@ -368,9 +366,10 @@ public LoanProductData mapRow(@NotNull final ResultSet rs, @SuppressWarnings("un final Integer repaymentStartDateTypeId = JdbcSupport.getInteger(rs, "repaymentStartDateType"); final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(repaymentStartDateTypeId); final boolean disableScheduleExtensionForDownPayment = rs.getBoolean("disableScheduleExtensionForDownPayment"); + final boolean enableInstallmentLevelDelinquency = rs.getBoolean("enableInstallmentLevelDelinquency"); String status = ""; - if (closeDate != null && closeDate.isBefore(DateUtils.getBusinessLocalDate())) { + if (closeDate != null && DateUtils.isBeforeBusinessDate(closeDate)) { status = "loanProduct.inActive"; } else { status = "loanProduct.active"; @@ -525,7 +524,8 @@ public LoanProductData mapRow(@NotNull final ResultSet rs, @SuppressWarnings("un maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageForDownPayment, - enableAutoRepaymentForDownPayment, advancedPaymentData, repaymentStartDateType, disableScheduleExtensionForDownPayment); + enableAutoRepaymentForDownPayment, advancedPaymentData, repaymentStartDateType, disableScheduleExtensionForDownPayment, + enableInstallmentLevelDelinquency); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index 84033b1c427..a0151034442 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -26,12 +26,14 @@ import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType; import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil; import org.apache.fineract.infrastructure.event.business.domain.loan.product.LoanProductCreateBusinessEvent; @@ -61,18 +63,14 @@ import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator; import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.rate.domain.RateRepositoryWrapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@Slf4j @RequiredArgsConstructor public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanProductWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(LoanProductWritePlatformServiceJpaRepositoryImpl.class); private final PlatformSecurityContext context; private final LoanProductDataValidator fromApiJsonDeserializer; private final LoanProductRepository loanProductRepository; @@ -255,7 +253,7 @@ public CommandProcessingResult updateLoanProduct(final Long loanProductId, final } catch (final DataIntegrityViolationException | JpaSystemException dve) { handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); - return new CommandProcessingResult((long) -1); + return CommandProcessingResult.resourceResult(-1L); } catch (final PersistenceException dve) { Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); handleDataIntegrityIssues(command, throwable, dve); @@ -372,14 +370,12 @@ private void validateInputDates(final JsonCommand command) { final LocalDate startDate = command.localDateValueOfParameterNamed("startDate"); final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate"); - if (startDate != null && closeDate != null) { - if (closeDate.isBefore(startDate)) { - throw new LoanProductDateException(startDate.toString(), closeDate.toString()); - } + if (closeDate != null && DateUtils.isBefore(closeDate, startDate)) { + throw new LoanProductDateException(startDate.toString(), closeDate.toString()); } } private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) { - LOG.error("Error occurred.", dve); + log.error("Error occurred.", dve); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/starter/LoanProductConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/starter/LoanProductConfiguration.java new file mode 100644 index 00000000000..e1e465e16f0 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/starter/LoanProductConfiguration.java @@ -0,0 +1,87 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.starter; + +import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; +import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; +import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper; +import org.apache.fineract.portfolio.fund.domain.FundRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.apache.fineract.portfolio.loanproduct.domain.AdvancedPaymentAllocationsJsonParser; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator; +import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService; +import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformServiceImpl; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformServiceImpl; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductWritePlatformService; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.rate.domain.RateRepositoryWrapper; +import org.apache.fineract.portfolio.rate.service.RateReadService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class LoanProductConfiguration { + + @Bean + @ConditionalOnMissingBean(LoanDropdownReadPlatformService.class) + public LoanDropdownReadPlatformService loanDropdownReadPlatformService( + LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) { + return new LoanDropdownReadPlatformServiceImpl(loanRepaymentScheduleTransactionProcessorFactory); + } + + @Bean + @ConditionalOnMissingBean(LoanProductReadPlatformService.class) + public LoanProductReadPlatformService loanProductReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate, + ChargeReadPlatformService chargeReadPlatformService, RateReadService rateReadService, DatabaseSpecificSQLGenerator sqlGenerator, + FineractEntityAccessUtil fineractEntityAccessUtil, DelinquencyReadPlatformService delinquencyReadPlatformService, + LoanProductRepository loanProductRepository) { + return new LoanProductReadPlatformServiceImpl(context, jdbcTemplate, chargeReadPlatformService, rateReadService, sqlGenerator, + fineractEntityAccessUtil, delinquencyReadPlatformService, loanProductRepository); + } + + @Bean + @ConditionalOnMissingBean(LoanProductWritePlatformService.class) + public LoanProductWritePlatformService loanProductWritePlatformService(PlatformSecurityContext context, + LoanProductDataValidator fromApiJsonDeserializer, LoanProductRepository loanProductRepository, AprCalculator aprCalculator, + FundRepository fundRepository, ChargeRepositoryWrapper chargeRepository, RateRepositoryWrapper rateRepository, + ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService, + FineractEntityAccessUtil fineractEntityAccessUtil, FloatingRateRepositoryWrapper floatingRateRepository, + LoanRepositoryWrapper loanRepositoryWrapper, BusinessEventNotifierService businessEventNotifierService, + DelinquencyBucketRepository delinquencyBucketRepository, + LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + AdvancedPaymentAllocationsJsonParser advancedPaymentJsonParser) { + return new LoanProductWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, loanProductRepository, aprCalculator, + fundRepository, chargeRepository, rateRepository, accountMappingWritePlatformService, fineractEntityAccessUtil, + floatingRateRepository, loanRepositoryWrapper, businessEventNotifierService, delinquencyBucketRepository, + loanRepaymentScheduleTransactionProcessorFactory, advancedPaymentJsonParser); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/Meeting.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/Meeting.java index 0ffe29016cd..3a758d813c5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/Meeting.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/domain/Meeting.java @@ -167,7 +167,7 @@ public LocalDate getMeetingDate() { } public boolean isMeetingDateBefore(final LocalDate newStartDate) { - return this.meetingDate != null && newStartDate != null && getMeetingDateLocalDate().isBefore(newStartDate) ? true : false; + return this.meetingDate != null && DateUtils.isBefore(getMeetingDateLocalDate(), newStartDate); } private static boolean isValidMeetingDate(final CalendarInstance calendarInstance, final LocalDate meetingDate, @@ -181,7 +181,7 @@ private static boolean isValidMeetingDate(final CalendarInstance calendarInstanc } private boolean isMeetingDateAfter(final LocalDate date) { - return getMeetingDateLocalDate().isAfter(date); + return DateUtils.isAfter(getMeetingDateLocalDate(), date); } public Collection getClientsAttendance() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java index e6d2b71e79b..11e7e22db8a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java @@ -38,7 +38,7 @@ static final class GetNotesNoteType { private GetNotesNoteType() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "noteType.client") public String code; @Schema(example = "Client note") @@ -46,20 +46,20 @@ private GetNotesNoteType() {} } @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "1") - public Integer clientId; + public Long clientId; public GetNotesNoteType noteType; @Schema(example = "First note edited") public String note; @Schema(example = "1") - public Integer createdById; + public Long createdById; @Schema(example = "mifos") public String createdByUsername; @Schema(example = "1342498505000") public ZonedDateTime createdOn; @Schema(example = "1") - public Integer updatedById; + public Long updatedById; @Schema(example = "mifos") public String updatedByUsername; @Schema(example = "1342498517000") @@ -72,20 +72,20 @@ public static final class GetResourceTypeResourceIdNotesNoteIdResponse { private GetResourceTypeResourceIdNotesNoteIdResponse() {} @Schema(example = "76") - public Integer id; + public Long id; @Schema(example = "1") - public Integer clientId; + public Long clientId; public GetResourceTypeResourceIdNotesResponse.GetNotesNoteType noteType; @Schema(example = "a note about the client") public String note; @Schema(example = "1") - public Integer createdById; + public Long createdById; @Schema(example = "mifos") public String createdByUsername; @Schema(example = "1359463135000") public ZonedDateTime createdOn; @Schema(example = "1") - public Integer updatedById; + public Long updatedById; @Schema(example = "mifos") public String updatedByUsername; @Schema(example = "1359463135000") @@ -107,9 +107,9 @@ public static final class PostResourceTypeResourceIdNotesResponse { private PostResourceTypeResourceIdNotesResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "76") public Integer resourceId; } @@ -137,9 +137,9 @@ private PutNotesChanges() {} } @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "76") public Integer resourceId; public PutNotesChanges changes; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java index 772e7496e59..d2d498fdb84 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java @@ -19,30 +19,22 @@ package org.apache.fineract.portfolio.paymentdetail.service; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.portfolio.paymentdetail.PaymentDetailConstants; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailRepository; import org.apache.fineract.portfolio.paymenttype.domain.PaymentType; import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class PaymentDetailWritePlatformServiceJpaRepositoryImpl implements PaymentDetailWritePlatformService { private final PaymentDetailRepository paymentDetailRepository; // private final CodeValueRepositoryWrapper codeValueRepositoryWrapper; private final PaymentTypeRepositoryWrapper paymentTyperepositoryWrapper; - @Autowired - public PaymentDetailWritePlatformServiceJpaRepositoryImpl(final PaymentDetailRepository paymentDetailRepository, - final PaymentTypeRepositoryWrapper paymentTyperepositoryWrapper) { - this.paymentDetailRepository = paymentDetailRepository; - this.paymentTyperepositoryWrapper = paymentTyperepositoryWrapper; - } - @Override public PaymentDetail createPaymentDetail(final JsonCommand command, final Map changes) { final Long paymentTypeId = command.longValueOfParameterNamed(PaymentDetailConstants.paymentTypeParamName); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/starter/PaymentDetailConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/starter/PaymentDetailConfiguration.java new file mode 100644 index 00000000000..ae5842dc7e9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/starter/PaymentDetailConfiguration.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.paymentdetail.starter; + +import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailRepository; +import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; +import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class PaymentDetailConfiguration { + + @Bean + @ConditionalOnMissingBean(PaymentDetailWritePlatformService.class) + PaymentDetailWritePlatformService paymentDetailWritePlatformService(PaymentDetailRepository paymentDetailRepository, + PaymentTypeRepositoryWrapper paymentTyperepositoryWrapper) { + return new PaymentDetailWritePlatformServiceJpaRepositoryImpl(paymentDetailRepository, paymentTyperepositoryWrapper); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java index a7edbd5d4b8..9b6588d0d4f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java @@ -27,9 +27,7 @@ import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper; import org.apache.fineract.portfolio.paymenttype.mapper.PaymentTypeMapper; import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class PaymentTypeReadPlatformServiceImpl implements PaymentTypeReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java index aa5a893c682..019b09cf5c8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java @@ -32,9 +32,7 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; -@Service @RequiredArgsConstructor public class PaymentTypeWriteServiceImpl implements PaymentTypeWriteService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/starter/PaymentTypeConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/starter/PaymentTypeConfiguration.java new file mode 100644 index 00000000000..c6c8ad52350 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/starter/PaymentTypeConfiguration.java @@ -0,0 +1,50 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.paymenttype.starter; + +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeDataValidator; +import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository; +import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper; +import org.apache.fineract.portfolio.paymenttype.mapper.PaymentTypeMapper; +import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService; +import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformServiceImpl; +import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeWriteService; +import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeWriteServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class PaymentTypeConfiguration { + + @Bean + @ConditionalOnMissingBean(PaymentTypeReadPlatformService.class) + PaymentTypeReadPlatformService paymentTypeReadPlatformService(PlatformSecurityContext context, PaymentTypeMapper paymentTypeMapper, + PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) { + return new PaymentTypeReadPlatformServiceImpl(context, paymentTypeMapper, paymentTypeRepositoryWrapper); + } + + @Bean + @ConditionalOnMissingBean(PaymentTypeWriteService.class) + PaymentTypeWriteService paymentTypeWriteService(PaymentTypeRepository repository, PaymentTypeRepositoryWrapper repositoryWrapper, + PaymentTypeDataValidator fromApiJsonDeserializer) { + return new PaymentTypeWriteServiceImpl(repository, repositoryWrapper, fromApiJsonDeserializer); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/api/ProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/api/ProductsApiResourceSwagger.java index 22f7d490b03..4dbe212eda6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/api/ProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/products/api/ProductsApiResourceSwagger.java @@ -58,7 +58,7 @@ static final class GetProductsMarketPrice { private GetProductsMarketPrice() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "Feb 1, 2016") public String fromDate; @Schema(example = "1") @@ -92,7 +92,7 @@ static final class GetChargeTimeType { private GetChargeTimeType() {} @Schema(example = "13") - public Integer id; + public Long id; @Schema(example = "chargeTimeType.activation") public String code; @Schema(example = "Share Account Activate") @@ -104,7 +104,7 @@ static final class GetChargeAppliesTo { private GetChargeAppliesTo() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "chargeAppliesTo.shares") public String code; @Schema(example = "Shares") @@ -116,7 +116,7 @@ static final class GetChargeCalculationType { private GetChargeCalculationType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "chargeCalculationType.flat") public String code; @Schema(example = "Flat") @@ -128,7 +128,7 @@ static final class GetChargePaymentMode { private GetChargePaymentMode() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "chargepaymentmode.regular") public String code; @Schema(example = "Regular") @@ -136,7 +136,7 @@ private GetChargePaymentMode() {} } @Schema(example = "20") - public Integer id; + public Long id; @Schema(example = "Share Account Activation Flat") public String name; @Schema(example = "true") @@ -157,7 +157,7 @@ static final class GetLockPeriodTypeEnum { private GetLockPeriodTypeEnum() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "Days") public String description; } @@ -167,7 +167,7 @@ static final class GetProductsAccountingRule { private GetProductsAccountingRule() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountingRuleType.cash") public String code; @Schema(example = "CASH BASED") @@ -183,7 +183,7 @@ static final class GetShareReferenceId { private GetShareReferenceId() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "ACCOUNT_NAME_1FJBQ") public String name; @Schema(example = "ASSET_ED1461237837829") @@ -195,7 +195,7 @@ static final class GetIncomeFromFeeAccountId { private GetIncomeFromFeeAccountId() {} @Schema(example = "14") - public Integer id; + public Long id; @Schema(example = "ACCOUNT_NAME_1FJBQ") public String name; @Schema(example = "INCOME_OY1461237869836") @@ -207,7 +207,7 @@ static final class GetShareEquityId { private GetShareEquityId() {} @Schema(example = "66") - public Integer id; + public Long id; @Schema(example = "Equity Account") public String name; @Schema(example = "EQUITY1") @@ -219,7 +219,7 @@ static final class GetShareSuspenseId { private GetShareSuspenseId() {} @Schema(example = "8") - public Integer id; + public Long id; @Schema(example = "ACCOUNT_NAME_1FJBQ") public String name; @Schema(example = "LIABILITY_MA1461237860198") @@ -237,7 +237,7 @@ static final class GetProductsMinimumActivePeriodFrequencyTypeOptions { private GetProductsMinimumActivePeriodFrequencyTypeOptions() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "savings.lockin.sharePeriodFrequencyType.days") public String code; @Schema(example = "Days") @@ -257,7 +257,7 @@ static final class GetProductsLiabilityType { private GetProductsLiabilityType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountType.liability") public String code; @Schema(example = "LIABILITY") @@ -269,7 +269,7 @@ static final class GetProductsLiabilityUsage { private GetProductsLiabilityUsage() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "accountUsage.detail") public String code; @Schema(example = "DETAIL") @@ -281,13 +281,13 @@ static final class GetProductsTagId { private GetProductsTagId() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "false") public Boolean isActive; } @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "ACCOUNT_NAME_1FJBQ") public String name; @Schema(example = "LIABILITY_2T1461237838897") @@ -314,7 +314,7 @@ static final class GetAssetType { private GetAssetType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "accountType.asset") public String code; @Schema(example = "ASSET") @@ -322,7 +322,7 @@ private GetAssetType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "ACCOUNT_NAME_1FJBQ") public String name; @Schema(example = "ASSET_ED1461237837829") @@ -349,7 +349,7 @@ static final class GetIncomeType { private GetIncomeType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "accountType.income") public String code; @Schema(example = "INCOME") @@ -357,7 +357,7 @@ private GetIncomeType() {} } @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "ACCOUNT_NAME_1FJBQ") public String name; @Schema(example = "INCOME_9O1461237838422") @@ -384,7 +384,7 @@ static final class GetEquityType { private GetEquityType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "accountType.equity") public String code; @Schema(example = "EQUITY") @@ -392,7 +392,7 @@ private GetEquityType() {} } @Schema(example = "66") - public Integer id; + public Long id; @Schema(example = "Equity Account") public String name; @Schema(example = "EQUITY1") @@ -415,7 +415,7 @@ private GetEquityType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Share Product") public String name; @Schema(example = "SP") @@ -466,7 +466,7 @@ static final class GetProductsPageItems { private GetProductsPageItems() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Share Product") public String name; @Schema(example = "Share Product Description") @@ -504,7 +504,7 @@ static final class PostProductsChargesSelected { private PostProductsChargesSelected() {} @Schema(example = "20") - public Integer id; + public Long id; } @Schema(example = "Share Product") @@ -555,7 +555,7 @@ public static final class PostProductsTypeResponse { private PostProductsTypeResponse() {} @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutProductsTypeProductIdRequest") @@ -589,7 +589,7 @@ private PutProductsChanges() {} } @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PutProductsChanges changes; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java index 4404e99b462..e769238984c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java @@ -24,25 +24,18 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.rate.domain.RateRepositoryWrapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class RateAssembler { private final FromJsonHelper fromApiJsonHelper; private final RateRepositoryWrapper rateRepository; - @Autowired - public RateAssembler(final FromJsonHelper fromApiJsonHelper, final RateRepositoryWrapper rateRepository) { - this.fromApiJsonHelper = fromApiJsonHelper; - this.rateRepository = rateRepository; - } - public List fromParsedJson(final JsonElement element) { final List rateItems = new ArrayList<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java index 67b57ff7c24..f2f52a81fa5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java @@ -24,32 +24,25 @@ import java.sql.SQLException; import java.util.Collection; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.rate.data.RateData; import org.apache.fineract.portfolio.rate.domain.RateAppliesTo; import org.apache.fineract.portfolio.rate.exception.RateNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; /** * Bowpi GT Created by Jose on 19/07/2017. */ -@Service +@RequiredArgsConstructor public class RateReadServiceImpl implements RateReadService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - @Autowired - public RateReadServiceImpl(PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - } - @Override public Collection retrieveAllRates() { this.context.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java index a5d816569dd..74429555bdd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java @@ -22,6 +22,8 @@ import jakarta.persistence.PersistenceException; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -35,36 +37,22 @@ import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUserRepository; import org.apache.fineract.useradministration.exception.UserNotFoundException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * Bowpi GT Created by Jose on 19/07/2017. */ -@Service +@RequiredArgsConstructor +@Slf4j public class RateWriteServiceImpl implements RateWriteService { - private static final Logger LOG = LoggerFactory.getLogger(RateWriteServiceImpl.class); - private final RateRepository rateRepository; private final AppUserRepository appUserRepository; private final PlatformSecurityContext context; private final RateDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer; - @Autowired - public RateWriteServiceImpl(RateRepository rateRepository, AppUserRepository appUserRepository, - final RateDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer, PlatformSecurityContext context) { - this.rateRepository = rateRepository; - this.appUserRepository = appUserRepository; - this.context = context; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - } - @Override public CommandProcessingResult createRate(JsonCommand command) { try { @@ -125,7 +113,7 @@ public CommandProcessingResult updateRate(final Long rateId, final JsonCommand c } catch (final JpaSystemException | DataIntegrityViolationException dve) { handleRateDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); - return new CommandProcessingResult((long) -1); + return CommandProcessingResult.resourceResult(-1L); } catch (final PersistenceException dve) { Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); handleRateDataIntegrityIssues(command, throwable, dve); @@ -143,7 +131,7 @@ private void handleRateDataIntegrityIssues(final JsonCommand command, final Thro "A rate with name '" + name + "' already exists", "name", name); } - LOG.error("Error due to Exception", dve); + log.error("Error due to Exception", dve); throw new PlatformDataIntegrityException("error.msg.fund.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/starter/RateConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/starter/RateConfiguration.java new file mode 100644 index 00000000000..87002355d26 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/starter/RateConfiguration.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.rate.starter; + +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.rate.domain.RateRepository; +import org.apache.fineract.portfolio.rate.domain.RateRepositoryWrapper; +import org.apache.fineract.portfolio.rate.serialization.RateDefinitionCommandFromApiJsonDeserializer; +import org.apache.fineract.portfolio.rate.service.RateAssembler; +import org.apache.fineract.portfolio.rate.service.RateReadService; +import org.apache.fineract.portfolio.rate.service.RateReadServiceImpl; +import org.apache.fineract.portfolio.rate.service.RateWriteService; +import org.apache.fineract.portfolio.rate.service.RateWriteServiceImpl; +import org.apache.fineract.useradministration.domain.AppUserRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class RateConfiguration { + + @Bean + @ConditionalOnMissingBean(RateAssembler.class) + public RateAssembler rateAssembler(FromJsonHelper fromApiJsonHelper, RateRepositoryWrapper rateRepository) { + return new RateAssembler(fromApiJsonHelper, rateRepository); + } + + @Bean + @ConditionalOnMissingBean(RateReadService.class) + public RateReadService rateReadService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context) { + return new RateReadServiceImpl(jdbcTemplate, context); + } + + @Bean + @ConditionalOnMissingBean(RateWriteService.class) + public RateWriteService rateWriteService(RateRepository rateRepository, AppUserRepository appUserRepository, + PlatformSecurityContext context, RateDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer) { + return new RateWriteServiceImpl(rateRepository, appUserRepository, context, fromApiJsonDeserializer); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksAssembler.java index 8282ce98758..002e21c8c48 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksAssembler.java @@ -27,23 +27,17 @@ import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class RepaymentWithPostDatedChecksAssembler { private final FromJsonHelper fromApiJsonHelper; - @Autowired - public RepaymentWithPostDatedChecksAssembler(final FromJsonHelper fromJsonHelper) { - this.fromApiJsonHelper = fromJsonHelper; - } - public Set fromParsedJson(final String json, final Loan loan) { final Set postDatedChecks = new HashSet<>(); final JsonElement jsonElement = this.fromApiJsonHelper.parse(json); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformServiceImpl.java index b3f16072051..e2995f6005d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformServiceImpl.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; @@ -30,11 +31,9 @@ import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.exception.PostDatedCheckNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor @Transactional(readOnly = true) public class RepaymentWithPostDatedChecksReadPlatformServiceImpl implements RepaymentWithPostDatedChecksReadPlatformService { @@ -42,15 +41,6 @@ public class RepaymentWithPostDatedChecksReadPlatformServiceImpl implements Repa private final LoanRepository loanRepository; private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository; - @Autowired - public RepaymentWithPostDatedChecksReadPlatformServiceImpl(final PostDatedChecksRepository postDatedChecksRepository, - final LoanRepository loanRepository, - final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository) { - this.postDatedChecksRepository = postDatedChecksRepository; - this.loanRepository = loanRepository; - this.loanRepaymentScheduleInstallmentRepository = loanRepaymentScheduleInstallmentRepository; - } - @Override public List getPostDatedChecks(final Long id) { final Loan loan = this.loanRepository.findById(id).orElseThrow(() -> new LoanNotFoundException(id)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformServiceImpl.java index 5c26cbe7eff..6b778f0225c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformServiceImpl.java @@ -30,6 +30,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -45,13 +46,11 @@ import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.exception.PostDatedCheckNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class RepaymentWithPostDatedChecksWritePlatformServiceImpl implements RepaymentWithPostDatedChecksWritePlatformService { public static final String NAME = "name"; @@ -71,14 +70,6 @@ public class RepaymentWithPostDatedChecksWritePlatformServiceImpl implements Rep private final FromJsonHelper fromApiJsonHelper; private final LoanRepository loanRepository; - @Autowired - public RepaymentWithPostDatedChecksWritePlatformServiceImpl(final PostDatedChecksRepository postDatedChecksRepository, - final FromJsonHelper fromApiJsonHelper, final LoanRepository loanRepository) { - this.postDatedChecksRepository = postDatedChecksRepository; - this.fromApiJsonHelper = fromApiJsonHelper; - this.loanRepository = loanRepository; - } - @Transactional @Override public CommandProcessingResult updatePostDatedChecks(JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/starter/RepaymentWithPostDatedChecksConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/starter/RepaymentWithPostDatedChecksConfiguration.java new file mode 100644 index 00000000000..de3594c4d0f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/starter/RepaymentWithPostDatedChecksConfiguration.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.repaymentwithpostdatedchecks.starter; + +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksReadPlatformService; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksReadPlatformServiceImpl; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksWritePlatformService; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksWritePlatformServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RepaymentWithPostDatedChecksConfiguration { + + @Bean + @ConditionalOnMissingBean(RepaymentWithPostDatedChecksAssembler.class) + public RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler(FromJsonHelper fromJsonHelper) { + return new RepaymentWithPostDatedChecksAssembler(fromJsonHelper); + } + + @Bean + @ConditionalOnMissingBean(RepaymentWithPostDatedChecksReadPlatformService.class) + public RepaymentWithPostDatedChecksReadPlatformService repaymentWithPostDatedChecksReadPlatformService( + PostDatedChecksRepository postDatedChecksRepository, LoanRepository loanRepository, + LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository) { + return new RepaymentWithPostDatedChecksReadPlatformServiceImpl(postDatedChecksRepository, loanRepository, + loanRepaymentScheduleInstallmentRepository); + } + + @Bean + @ConditionalOnMissingBean(RepaymentWithPostDatedChecksWritePlatformService.class) + public RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService( + PostDatedChecksRepository postDatedChecksRepository, FromJsonHelper fromApiJsonHelper, LoanRepository loanRepository) { + return new RepaymentWithPostDatedChecksWritePlatformServiceImpl(postDatedChecksRepository, fromApiJsonHelper, loanRepository); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java index 8872788b628..f2cd30cebb8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java @@ -192,7 +192,8 @@ public String retrieveOne(@PathParam("accountId") @Parameter(description = "acco this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); - if (!(CommandParameterUtil.is(chargeStatus, "all") || CommandParameterUtil.is(chargeStatus, "active") || CommandParameterUtil.is(chargeStatus, "inactive"))) { + if (!(CommandParameterUtil.is(chargeStatus, "all") || CommandParameterUtil.is(chargeStatus, "active") + || CommandParameterUtil.is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); } @@ -369,10 +370,10 @@ public String handleCommands(@PathParam("accountId") @Parameter(description = "a if (commandRequest == null) { throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "undoactivate", - "calculateInterest", "postInterest", "close", "prematureClose", "calculatePrematureAmount" }); + new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "undoactivate", + "calculateInterest", "postInterest", "close", "prematureClose", "calculatePrematureAmount" }); } - + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java index cd354758288..3249c2b8192 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java @@ -39,13 +39,13 @@ static final class GetFixedDepositAccountsProductOptions { private GetFixedDepositAccountsProductOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Passbook Savings") public String name; } @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "small business") public String clientName; public Set productOptions; @@ -61,7 +61,7 @@ static final class GetFixedDepositAccountsStatus { private GetFixedDepositAccountsStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "savingsAccountStatusType.submitted.and.pending.approval") public String code; @Schema(example = "Submitted and pending approval") @@ -125,7 +125,7 @@ static final class GetFixedDepositAccountsInterestCompoundingPeriodType { private GetFixedDepositAccountsInterestCompoundingPeriodType() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "savings.interest.period.savingsCompoundingInterestPeriodType.monthly") public String code; @Schema(example = "Monthly") @@ -137,7 +137,7 @@ static final class GetFixedDepositAccountsInterestPostingPeriodType { private GetFixedDepositAccountsInterestPostingPeriodType() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly") public String code; @Schema(example = "Monthly") @@ -149,7 +149,7 @@ static final class GetFixedDepositAccountsInterestCalculationType { private GetFixedDepositAccountsInterestCalculationType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "savingsInterestCalculationType.dailybalance") public String code; @Schema(example = "Daily Balance") @@ -161,7 +161,7 @@ static final class GetFixedDepositAccountsInterestCalculationDaysInYearType { private GetFixedDepositAccountsInterestCalculationDaysInYearType() {} @Schema(example = "365") - public Integer id; + public Long id; @Schema(example = "savingsInterestCalculationDaysInYearType.days365") public String code; @Schema(example = "365 Days") @@ -182,7 +182,7 @@ static final class GetFixedDepositAccountsMinDepositTermType { private GetFixedDepositAccountsMinDepositTermType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.months") public String code; @Schema(example = "Months") @@ -194,7 +194,7 @@ static final class GetFixedDepositAccountsMaxDepositTermType { private GetFixedDepositAccountsMaxDepositTermType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.years") public String code; @Schema(example = "Years") @@ -206,7 +206,7 @@ static final class GetFixedDepositAccountsDepositPeriodFrequency { private GetFixedDepositAccountsDepositPeriodFrequency() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "deposit.period.savingsPeriodFrequencyType.months") public String code; @Schema(example = "Months") @@ -214,19 +214,19 @@ private GetFixedDepositAccountsDepositPeriodFrequency() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "000000001") public Long accountNo; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "Sangamesh N") public String clientName; @Schema(example = "3") - public Integer savingsProductId; + public Long savingsProductId; @Schema(example = "FD01") public String savingsProductName; @Schema(example = "0") - public Integer fieldOfficerId; + public Long fieldOfficerId; public GetFixedDepositAccountsStatus status; public GetFixedDepositAccountsTimeline timeline; public GetFixedDepositAccountsCurrency currency; @@ -262,9 +262,9 @@ public static final class PostFixedDepositAccountsRequest { private PostFixedDepositAccountsRequest() {} @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "en") public String locale; @Schema(example = "dd MMMM yyyy") @@ -276,7 +276,7 @@ private PostFixedDepositAccountsRequest() {} @Schema(example = "6") public Integer depositPeriod; @Schema(example = "2") - public Integer depositPeriodFrequencyId; + public Long depositPeriodFrequencyId; } @Schema(description = "PostFixedDepositAccountsResponse") @@ -285,13 +285,13 @@ public static final class PostFixedDepositAccountsResponse { private PostFixedDepositAccountsResponse() {} @Schema(example = "2") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "GetFixedDepositAccountsAccountIdResponse") @@ -312,7 +312,7 @@ static final class GetFixedDepositAccountsPeriodType { private GetFixedDepositAccountsPeriodType() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "interestChartPeriodType.days") public String code; @Schema(example = "Days") @@ -338,7 +338,7 @@ private GetFixedDepositAccountsAccountChartCurrency() {} } @Schema(example = "13") - public Integer id; + public Long id; public GetFixedDepositAccountsPeriodType periodType; @Schema(example = "181") public Integer fromPeriod; @@ -354,7 +354,7 @@ static final class GetFixedDepositAccountsPeriodTypes { private GetFixedDepositAccountsPeriodTypes() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "interestChartPeriodType.days") public String code; @Schema(example = "Days") @@ -362,11 +362,11 @@ private GetFixedDepositAccountsPeriodTypes() {} } @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "[2013, 10, 2]") public LocalDate fromDate; @Schema(example = "5") - public Integer accountId; + public Long accountId; @Schema(example = "FD000023") public Long accountNumber; public Set chartSlabs; @@ -403,21 +403,21 @@ private GetFixedDepositAccountsAccountIdSummary() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "FD000023") public Long accountNo; @Schema(example = "FD-23") public String externalId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "Sangamesh N") public String clientName; @Schema(example = "3") - public Integer savingsProductId; + public Long savingsProductId; @Schema(example = "FD01") public String savingsProductName; @Schema(example = "0") - public Integer fieldOfficerId; + public Long fieldOfficerId; public GetFixedDepositAccountsResponse.GetFixedDepositAccountsStatus status; public GetFixedDepositAccountsResponse.GetFixedDepositAccountsTimeline timeline; public GetFixedDepositAccountsAccountIdCurrency currency; @@ -475,13 +475,13 @@ private PutFixedDepositAccountsChanges() {} } @Schema(example = "2") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PutFixedDepositAccountsChanges changes; } @@ -497,13 +497,13 @@ public static final class PostFixedDepositAccountsAccountIdResponse { private PostFixedDepositAccountsAccountIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "DeleteFixedDepositAccountsAccountIdResponse") @@ -512,10 +512,10 @@ public static final class DeleteFixedDepositAccountsAccountIdResponse { private DeleteFixedDepositAccountsAccountIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java index c8f1b4f9c34..38713039076 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java @@ -178,7 +178,7 @@ static final class GetFixedDepositProductsMinDepositTermType { private GetFixedDepositProductsMinDepositTermType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.months") public String code; @Schema(example = "Months") @@ -190,7 +190,7 @@ static final class GetFixedDepositProductsMaxDepositTermType { private GetFixedDepositProductsMaxDepositTermType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.years") public String code; @Schema(example = "Years") @@ -202,7 +202,7 @@ static final class GetFixedDepositProductsInterestCompoundingPeriodType { private GetFixedDepositProductsInterestCompoundingPeriodType() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "savings.interest.period.savingsCompoundingInterestPeriodType.monthly") public String code; @Schema(example = "Monthly") @@ -214,7 +214,7 @@ static final class GetFixedDepositProductsInterestPostingPeriodType { private GetFixedDepositProductsInterestPostingPeriodType() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly") public String code; @Schema(example = "Monthly") @@ -226,7 +226,7 @@ static final class GetFixedDepositProductsInterestCalculationType { private GetFixedDepositProductsInterestCalculationType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "savingsInterestCalculationType.dailybalance") public String code; @Schema(example = "Daily Balance") @@ -238,7 +238,7 @@ static final class GetFixedDepositProductsInterestCalculationDaysInYearType { private GetFixedDepositProductsInterestCalculationDaysInYearType() {} @Schema(example = "365") - public Integer id; + public Long id; @Schema(example = "savingsInterestCalculationDaysInYearType.days365") public String code; @Schema(example = "365 Days") @@ -250,7 +250,7 @@ static final class GetFixedDepositProductsAccountingRule { private GetFixedDepositProductsAccountingRule() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "accountingRuleType.none") public String code; @Schema(example = "NONE") @@ -258,7 +258,7 @@ private GetFixedDepositProductsAccountingRule() {} } @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "FD01") public String name; @Schema(example = "FD01") @@ -311,7 +311,7 @@ static final class GetFixedDepositProductsProductIdInterestCompoundingPeriodType private GetFixedDepositProductsProductIdInterestCompoundingPeriodType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "savings.interest.period.savingsCompoundingInterestPeriodType.daily") public String code; @Schema(example = "Daily") @@ -354,7 +354,7 @@ static final class GetFixedDepositProductsProductIdFeeToIncomeAccountMappingsCha private GetFixedDepositProductsProductIdFeeToIncomeAccountMappingsCharge() {} @Schema(example = "11") - public Integer id; + public Long id; @Schema(example = "sav charge") public String name; @Schema(example = "false") @@ -376,7 +376,7 @@ static final class GetFixedDepositProductsProductIdPenaltyToIncomeAccountMapping private GetFixedDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge() {} @Schema(example = "12") - public Integer id; + public Long id; @Schema(example = "sav 2") public String name; @Schema(example = "false") @@ -394,7 +394,7 @@ static final class GetFixedDepositProductsProductIdPreClosurePenalInterestOnType private GetFixedDepositProductsProductIdPreClosurePenalInterestOnType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "preClosurePenalInterestOnType.wholeTerm") public String code; @Schema(example = "Whole term") @@ -406,7 +406,7 @@ static final class GetFixedDepositProductsProductIdMinDepositTermType { private GetFixedDepositProductsProductIdMinDepositTermType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.weeks") public String code; @Schema(example = "Weeks") @@ -418,7 +418,7 @@ static final class GetFixedDepositProductsProductIdMaxDepositTermType { private GetFixedDepositProductsProductIdMaxDepositTermType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.years") public String code; @Schema(example = "Years") @@ -438,7 +438,7 @@ static final class GetFixedDepositProductsProductIdPeriodType { private GetFixedDepositProductsProductIdPeriodType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "interestChartPeriodType.weeks") public String code; @Schema(example = "Weeks") @@ -446,7 +446,7 @@ private GetFixedDepositProductsProductIdPeriodType() {} } @Schema(example = "18") - public Integer id; + public Long id; @Schema(example = "from 0 to 90 days") public String description; public GetFixedDepositProductsProductIdPeriodType periodType; @@ -460,11 +460,11 @@ private GetFixedDepositProductsProductIdPeriodType() {} } @Schema(example = "8") - public Integer id; + public Long id; @Schema(example = "[2014, 1, 1]") public LocalDate fromDate; @Schema(example = "8") - public Integer savingsProductId; + public Long savingsProductId; @Schema(example = "Fixed deposit product") public String savingsProductName; public Set chartSlabs; @@ -472,7 +472,7 @@ private GetFixedDepositProductsProductIdPeriodType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Fixed deposit product") public String name; @Schema(example = "FD01") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResourceSwagger.java index 918986970e8..83f52ae2005 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResourceSwagger.java @@ -40,7 +40,7 @@ static final class GetRecurringTransactionType { private GetRecurringTransactionType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "savingsAccountTransactionType.deposit") public String code; @Schema(example = "Deposit") @@ -90,10 +90,10 @@ private GetRecurringCurrency() {} } @Schema(example = "1") - public Integer id; + public Long id; public GetRecurringTransactionType transactionType; @Schema(example = "1") - public Integer accountId; + public Long accountId; @Schema(example = "000000001") public String accountNo; @Schema(example = "[2014, 6, 25]") @@ -137,7 +137,7 @@ static final class GetRecurringTransactionsTransactionType { private GetRecurringTransactionsTransactionType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "savingsAccountTransactionType.withdrawal") public String code; @Schema(example = "Withdrawal") @@ -161,13 +161,13 @@ static final class GetRecurringPaymentType { private GetRecurringPaymentType() {} @Schema(example = "11") - public Integer id; + public Long id; @Schema(example = "cash") public String name; } @Schema(example = "62") - public Integer id; + public Long id; public GetRecurringPaymentType paymentType; @Schema(example = "") public Integer accountNumber; @@ -182,10 +182,10 @@ private GetRecurringPaymentType() {} } @Schema(example = "1") - public Integer id; + public Long id; public GetRecurringTransactionsTransactionType transactionType; @Schema(example = "1") - public Integer accountId; + public Long accountId; @Schema(example = "000000001") public String accountNo; @Schema(example = "[2013, 8, 7]") @@ -249,13 +249,13 @@ private PostRecurringChanges() {} } @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "2") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "47") - public Integer resourceId; + public Long resourceId; public PostRecurringChanges changes; } @@ -265,13 +265,13 @@ public static final class PostRecurringDepositAccountsRecurringDepositAccountIdT private PostRecurringDepositAccountsRecurringDepositAccountIdTransactionsTransactionIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "2") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "48") - public Integer resourceId; + public Long resourceId; public PostRecurringDepositAccountsRecurringDepositAccountIdTransactionsResponse.PostRecurringChanges changes; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResourceSwagger.java index 35e9e85db2e..e435b6d6de3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResourceSwagger.java @@ -39,13 +39,13 @@ static final class GetRecurringProductOptions { private GetRecurringProductOptions() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Passbook Savings") public String name; } @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "small business") public String clientName; public Set productOptions; @@ -61,7 +61,7 @@ static final class GetRecurringDepositAccountsStatus { private GetRecurringDepositAccountsStatus() {} @Schema(example = "100") - public Integer id; + public Long id; @Schema(example = "savingsAccountStatusType.submitted.and.pending.approval") public String code; @Schema(example = "Submitted and pending approval") @@ -125,7 +125,7 @@ static final class GetRecurringDepositAccountsInterestCompoundingPeriodType { private GetRecurringDepositAccountsInterestCompoundingPeriodType() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "savings.interest.period.savingsCompoundingInterestPeriodType.monthly") public String code; @Schema(example = "Monthly") @@ -137,7 +137,7 @@ static final class GetRecurringDepositAccountsInterestPostingPeriodType { private GetRecurringDepositAccountsInterestPostingPeriodType() {} @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly") public String code; @Schema(example = "Monthly") @@ -149,7 +149,7 @@ static final class GetRecurringDepositAccountsInterestCalculationType { private GetRecurringDepositAccountsInterestCalculationType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "savingsInterestCalculationType.dailybalance") public String code; @Schema(example = "Daily Balance") @@ -161,7 +161,7 @@ static final class GetRecurringDepositAccountsInterestCalculationDaysInYearType private GetRecurringDepositAccountsInterestCalculationDaysInYearType() {} @Schema(example = "365") - public Integer id; + public Long id; @Schema(example = "savingsInterestCalculationDaysInYearType.days365") public String code; @Schema(example = "365 Days") @@ -182,7 +182,7 @@ static final class GetRecurringDepositAccountsMinDepositTermType { private GetRecurringDepositAccountsMinDepositTermType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.months") public String code; @Schema(example = "Months") @@ -194,7 +194,7 @@ static final class GetRecurringDepositAccountsMaxDepositTermType { private GetRecurringDepositAccountsMaxDepositTermType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.years") public String code; @Schema(example = "Years") @@ -206,7 +206,7 @@ static final class GetRecurringDepositAccountsDepositPeriodFrequency { private GetRecurringDepositAccountsDepositPeriodFrequency() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "deposit.period.savingsPeriodFrequencyType.months") public String code; @Schema(example = "Months") @@ -218,7 +218,7 @@ static final class GetRecurringDepositAccountsRecurringDepositFrequencyType { private GetRecurringDepositAccountsRecurringDepositFrequencyType() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "recurring.deposit.savingsPeriodFrequencyType.months") public String code; @Schema(example = "Months") @@ -226,19 +226,19 @@ private GetRecurringDepositAccountsRecurringDepositFrequencyType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "000000001") public Long accountNo; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "Sangamesh N") public String clientName; @Schema(example = "3") - public Integer savingsProductId; + public Long savingsProductId; @Schema(example = "RD01") public String savingsProductName; @Schema(example = "0") - public Integer fieldOfficerId; + public Long fieldOfficerId; public GetRecurringDepositAccountsStatus status; public GetRecurringDepositAccountsTimeline timeline; public GetRecurringDepositAccountsCurrency currency; @@ -277,9 +277,9 @@ public static final class PostRecurringDepositAccountsRequest { private PostRecurringDepositAccountsRequest() {} @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer productId; + public Long productId; @Schema(example = "en") public String locale; @Schema(example = "dd MMMM yyyy") @@ -308,13 +308,13 @@ public static final class PostRecurringDepositAccountsResponse { private PostRecurringDepositAccountsResponse() {} @Schema(example = "2") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "GetRecurringDepositAccountsAccountIdResponse") @@ -335,7 +335,7 @@ static final class GetRecurringDepositAccountsPeriodType { private GetRecurringDepositAccountsPeriodType() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "interestChartPeriodType.days") public String code; @Schema(example = "Days") @@ -361,7 +361,7 @@ private GetRecurringDepositAccountsAccountChartCurrency() {} } @Schema(example = "13") - public Integer id; + public Long id; public GetRecurringDepositAccountsPeriodType periodType; @Schema(example = "181") public Integer fromPeriod; @@ -377,7 +377,7 @@ static final class GetRecurringDepositAccountsPeriodTypes { private GetRecurringDepositAccountsPeriodTypes() {} @Schema(example = "0") - public Integer id; + public Long id; @Schema(example = "interestChartPeriodType.days") public String code; @Schema(example = "Days") @@ -385,11 +385,11 @@ private GetRecurringDepositAccountsPeriodTypes() {} } @Schema(example = "4") - public Integer id; + public Long id; @Schema(example = "[2013, 10, 2]") public LocalDate fromDate; @Schema(example = "5") - public Integer accountId; + public Long accountId; @Schema(example = "RD000023") public Long accountNumber; public Set chartSlabs; @@ -397,21 +397,21 @@ private GetRecurringDepositAccountsPeriodTypes() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "RD000023") public Long accountNo; @Schema(example = "RD-23") public String externalId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "Sangamesh N") public String clientName; @Schema(example = "3") - public Integer savingsProductId; + public Long savingsProductId; @Schema(example = "RD01") public String savingsProductName; @Schema(example = "0") - public Integer fieldOfficerId; + public Long fieldOfficerId; public GetRecurringDepositAccountsResponse.GetRecurringDepositAccountsStatus status; public GetRecurringDepositAccountsResponse.GetRecurringDepositAccountsTimeline timeline; public GetRecurringDepositAccountsResponse.GetRecurringDepositAccountsCurrency currency; @@ -468,13 +468,13 @@ private PutRecurringDepositAccountsChanges() {} } @Schema(example = "2") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; public PutRecurringDepositAccountsChanges changes; } @@ -490,13 +490,13 @@ public static final class PostRecurringDepositAccountsAccountIdResponse { private PostRecurringDepositAccountsAccountIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "DeleteRecurringDepositAccountsResponse") @@ -505,10 +505,10 @@ public static final class DeleteRecurringDepositAccountsResponse { private DeleteRecurringDepositAccountsResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java index 02622104a99..d3947027996 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java @@ -280,7 +280,7 @@ private GetRecurringDepositProductsRecurringDepositFrequencyType() {} } @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "RD01") public String name; @Schema(example = "RD01") @@ -378,7 +378,7 @@ static final class GetRecurringDepositProductsProductIdFeeToIncomeAccountMapping private GetRecurringDepositProductsProductIdFeeToIncomeAccountMappingsCharge() {} @Schema(example = "11") - public Integer id; + public Long id; @Schema(example = "sav charge") public String name; @Schema(example = "false") @@ -392,11 +392,11 @@ static final class GetRecurringDepositProductsProductIdFeeToIncomeAccountMapping private GetRecurringDepositProductsProductIdFeeToIncomeAccountMappingsIncomeAccount() {} @Schema(example = "16") - public Integer id; + public Long id; @Schema(example = "income from savings fee") public String name; @Schema(example = "24") - public Integer glCode; + public String glCode; } public GetRecurringDepositProductsProductIdFeeToIncomeAccountMappingsCharge charge; @@ -412,7 +412,7 @@ static final class GetRecurringDepositProductsProductIdPenaltyToIncomeAccountMap private GetRecurringDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge() {} @Schema(example = "12") - public Integer id; + public Long id; @Schema(example = "sav 2") public String name; @Schema(example = "false") @@ -430,7 +430,7 @@ static final class GetRecurringDepositProductsProductIdPreClosurePenalInterestOn private GetRecurringDepositProductsProductIdPreClosurePenalInterestOnType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "preClosurePenalInterestOnType.wholeTerm") public String code; @Schema(example = "Whole term") @@ -442,7 +442,7 @@ static final class GetRecurringDepositProductsProductIdMinDepositTermType { private GetRecurringDepositProductsProductIdMinDepositTermType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.weeks") public String code; @Schema(example = "Weeks") @@ -454,7 +454,7 @@ static final class GetRecurringDepositProductsProductIdMaxDepositTermType { private GetRecurringDepositProductsProductIdMaxDepositTermType() {} @Schema(example = "3") - public Integer id; + public Long id; @Schema(example = "deposit.term.savingsPeriodFrequencyType.years") public String code; @Schema(example = "Years") @@ -474,7 +474,7 @@ static final class GetRecurringDepositProductsProductIdPeriodType { private GetRecurringDepositProductsProductIdPeriodType() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "interestChartPeriodType.weeks") public String code; @Schema(example = "Weeks") @@ -482,7 +482,7 @@ private GetRecurringDepositProductsProductIdPeriodType() {} } @Schema(example = "18") - public Integer id; + public Long id; @Schema(example = "from 0 to 90 days") public String description; public GetRecurringDepositProductsProductIdPeriodType periodType; @@ -496,11 +496,11 @@ private GetRecurringDepositProductsProductIdPeriodType() {} } @Schema(example = "8") - public Integer id; + public Long id; @Schema(example = "[2014, 1, 1]") public LocalDate fromDate; @Schema(example = "8") - public Integer savingsProductId; + public Long savingsProductId; @Schema(example = "Recurring deposit product") public String savingsProductName; public Set chartSlabs; @@ -508,7 +508,7 @@ private GetRecurringDepositProductsProductIdPeriodType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "Recurring deposit product") public String name; @Schema(example = "RD01") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResourceSwagger.java index f8f817c2cf8..d1f45a01ba8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResourceSwagger.java @@ -74,11 +74,11 @@ private GetChargesChargeTimeType() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "3") - public Integer chargeId; + public Long chargeId; @Schema(example = "57") - public Integer accountId; + public Long accountId; @Schema(example = "Savings account maintenance fee") public String name; public GetChargesChargeTimeType chargeTimeType; @@ -138,7 +138,7 @@ private GetChargesAppliesTo() {} } @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "Passbook Fee") public String name; @Schema(example = "true") @@ -170,9 +170,9 @@ public static final class GetSavingsAccountsSavingsAccountIdChargesSavingsAccoun private GetSavingsAccountsSavingsAccountIdChargesSavingsAccountChargeIdResponse() {} @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "1") - public Integer chargeId; + public Long chargeId; @Schema(example = "Passbook fee") public String name; public GetSavingsAccountsSavingsAccountIdChargesResponse.GetChargesChargeTimeType chargeTimeType; @@ -204,7 +204,7 @@ public static final class PostSavingsAccountsSavingsAccountIdChargesRequest { private PostSavingsAccountsSavingsAccountIdChargesRequest() {} @Schema(example = "2") - public Integer chargeId; + public Long chargeId; @Schema(example = "en") public String locale; @Schema(example = "100") @@ -221,13 +221,13 @@ public static final class PostSavingsAccountsSavingsAccountIdChargesResponse { private PostSavingsAccountsSavingsAccountIdChargesResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "6") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PutSavingsAccountsSavingsAccountIdChargesSavingsAccountChargeIdRequest") @@ -265,13 +265,13 @@ private PutSavingsChanges() {} } @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "6") - public Integer resourceId; + public Long resourceId; public PutSavingsChanges changes; } @@ -296,13 +296,13 @@ public static final class PostSavingsAccountsSavingsAccountIdChargesSavingsAccou private PostSavingsAccountsSavingsAccountIdChargesSavingsAccountChargeIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "2") - public Integer resourceId; + public Long resourceId; } @Schema(description = "DeleteSavingsAccountsSavingsAccountIdChargesSavingsAccountChargeIdResponse") @@ -311,12 +311,12 @@ public static final class DeleteSavingsAccountsSavingsAccountIdChargesSavingsAcc private DeleteSavingsAccountsSavingsAccountIdChargesSavingsAccountChargeIdResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "2") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java index 42e737ebacb..95d346487b9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java @@ -50,7 +50,6 @@ import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; @@ -66,10 +65,8 @@ import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionSearchService; import org.apache.fineract.portfolio.search.data.AdvancedQueryRequest; import org.apache.fineract.portfolio.search.data.TransactionSearchRequest; -import org.springframework.dao.CannotAcquireLockException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Sort; -import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Component; @Path("/v1/savingsaccounts/{savingsId}/transactions") @@ -185,42 +182,33 @@ public String advancedQuery(@PathParam("savingsId") @Parameter(description = "sa @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountTransactionsApiResourceSwagger.PostSavingsAccountTransactionsResponse.class))) }) public String transaction(@PathParam("savingsId") final Long savingsId, @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { - try { - final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); - CommandProcessingResult result = null; - if (is(commandParam, "deposit")) { - final CommandWrapper commandRequest = builder.savingsAccountDeposit(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "gsimDeposit")) { - final CommandWrapper commandRequest = builder.gsimSavingsAccountDeposit(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawal")) { - final CommandWrapper commandRequest = builder.savingsAccountWithdrawal(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterestAsOn")) { - final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_HOLD_AMOUNT)) { - final CommandWrapper commandRequest = builder.holdAmount(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } - - if (result == null) { - // - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "deposit", "withdrawal", SavingsApiConstants.COMMAND_HOLD_AMOUNT }); - } + CommandProcessingResult result = null; + if (is(commandParam, "deposit")) { + final CommandWrapper commandRequest = builder.savingsAccountDeposit(savingsId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "gsimDeposit")) { + final CommandWrapper commandRequest = builder.gsimSavingsAccountDeposit(savingsId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "withdrawal")) { + final CommandWrapper commandRequest = builder.savingsAccountWithdrawal(savingsId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "postInterestAsOn")) { + final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(savingsId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_HOLD_AMOUNT)) { + final CommandWrapper commandRequest = builder.holdAmount(savingsId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } - return this.toApiJsonSerializer.serialize(result); - } catch (ObjectOptimisticLockingFailureException lockingFailureException) { - throw new PlatformDataIntegrityException("error.msg.savings.concurrent.operations", - "Concurrent Transactions being made on this savings account: " + lockingFailureException.getMessage(), - lockingFailureException); - } catch (CannotAcquireLockException cannotAcquireLockException) { - throw new PlatformDataIntegrityException("error.msg.savings.concurrent.operations.unable.to.acquire.lock", - "Unable to acquir lock for this transaction: " + cannotAcquireLockException.getMessage(), cannotAcquireLockException); + if (result == null) { + // + throw new UnrecognizedQueryParamException("command", commandParam, + new Object[] { "deposit", "withdrawal", SavingsApiConstants.COMMAND_HOLD_AMOUNT }); } + + return this.toApiJsonSerializer.serialize(result); } @POST diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java index 3e7f5323977..3b06298dadc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java @@ -216,13 +216,13 @@ public static final class PostSavingsAccountTransactionsResponse { private PostSavingsAccountTransactionsResponse() {} @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } @Schema(description = "PostSavingsAccountBulkReversalTransactionsRequest") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResourceSwagger.java index 5f54f53ba18..e584f6de4eb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResourceSwagger.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.savings.api; import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.Set; @@ -260,9 +261,9 @@ private GetSavingsAccountsSummary() {} public GetSavingsAccountsResponse.GetSavingsPageItems.GetSavingsCurrency currency; @Schema(example = "0") - public Integer accountBalance; + public BigDecimal accountBalance; @Schema(example = "0") - public Integer availableBalance; + public BigDecimal availableBalance; } @Schema(example = "1") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java index 898c444e8e3..9300e1edfbb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java @@ -295,6 +295,7 @@ private GetSavingsProductsFeeToIncomeAccountMappingsCharge() {} public GetSavingsProductsFeeToIncomeAccountMappingsCharge charge; public GetSavingsProductsGlAccount incomeAccount; } + static final class GetSavingsProductsCharge { private GetSavingsProductsCharge() {} @@ -330,7 +331,7 @@ private GetSavingsProductsPenaltyToIncomeAccountMappingsCharge() {} public GetSavingsProductsPenaltyToIncomeAccountMappingsCharge charge; public GetSavingsProductsGlAccount incomeAccount; } - + @Schema(example = "1") public Integer id; @Schema(example = "savings product") @@ -461,7 +462,7 @@ private GetSavingsProductsLiabilityTagId() {} @Schema(example = "Savings Control") public String name; @Schema(example = "50001") - public Integer glCode; + public String glCode; @Schema(example = "false") public Boolean disabled; @Schema(example = "true") @@ -499,7 +500,7 @@ private GetSavingsAssetTagId() {} @Schema(example = "Cash") public String name; @Schema(example = "100001") - public Integer glCode; + public String glCode; @Schema(example = "false") public Boolean disabled; @Schema(example = "true") @@ -530,7 +531,7 @@ private GetSavingsProductsExpenseType() {} @Schema(example = "Write Off Expenses") public String name; @Schema(example = "60001") - public Integer glCode; + public String glCode; @Schema(example = "false") public Boolean disabled; @Schema(example = "true") @@ -561,7 +562,7 @@ private GetSavingsProductsIncomeType() {} @Schema(example = "income from interest") public String name; @Schema(example = "40001") - public Integer glCode; + public String glCode; @Schema(example = "false") public Boolean disabled; @Schema(example = "true") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartData.java index 0b3bae56668..46adf4221e5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountInterestRateChartData.java @@ -23,6 +23,7 @@ import java.util.Collection; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData; import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartSlabData; @@ -159,12 +160,11 @@ public void addChartSlab(final DepositAccountInterestRateChartSlabData chartSlab if (this.chartSlabs == null) { this.chartSlabs = new ArrayList<>(); } - this.chartSlabs.add(chartSlab); } public boolean isFromDateAfter(final LocalDate compareDate) { - return (compareDate == null) ? false : this.fromDate.isAfter(compareDate); + return compareDate != null && DateUtils.isAfter(this.fromDate, compareDate); } public LocalDate endDate() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTO.java index 37b38a896cf..de3a62022e0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTO.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTO.java @@ -20,10 +20,8 @@ import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; -import org.apache.fineract.useradministration.domain.AppUser; public class SavingsAccountTransactionDTO { @@ -31,24 +29,9 @@ public class SavingsAccountTransactionDTO { private final LocalDate transactionDate; private final BigDecimal transactionAmount; private final PaymentDetail paymentDetail; - private final LocalDateTime createdDate; private final Long savingsAccountId; - private final AppUser appUser; private final Integer depositAccountType; - public SavingsAccountTransactionDTO(final DateTimeFormatter formatter, final LocalDate transactionDate, - final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final LocalDateTime createdDate, final AppUser appUser, - final Integer depositAccountType) { - this.formatter = formatter; - this.transactionDate = transactionDate; - this.transactionAmount = transactionAmount; - this.paymentDetail = paymentDetail; - this.createdDate = createdDate; - this.savingsAccountId = null; - this.appUser = appUser; - this.depositAccountType = depositAccountType; - } - /** * This constructor is used for bulk deposit transactions * @@ -56,19 +39,15 @@ public SavingsAccountTransactionDTO(final DateTimeFormatter formatter, final Loc * @param transactionDate * @param transactionAmount * @param paymentDetail - * @param createdDate * @param savingsAccountId */ public SavingsAccountTransactionDTO(DateTimeFormatter formatter, LocalDate transactionDate, BigDecimal transactionAmount, - PaymentDetail paymentDetail, LocalDateTime createdDate, Long savingsAccountId, AppUser appUser, - final Integer depositAccountType) { + PaymentDetail paymentDetail, Long savingsAccountId, final Integer depositAccountType) { this.formatter = formatter; this.transactionDate = transactionDate; this.transactionAmount = transactionAmount; this.paymentDetail = paymentDetail; - this.createdDate = createdDate; this.savingsAccountId = savingsAccountId; - this.appUser = appUser; this.depositAccountType = depositAccountType; } @@ -88,18 +67,10 @@ public PaymentDetail getPaymentDetail() { return this.paymentDetail; } - public LocalDateTime getCreatedDate() { - return this.createdDate; - } - public Long getSavingsAccountId() { return this.savingsAccountId; } - public AppUser getAppUser() { - return this.appUser; - } - public Integer getAccountType() { return this.depositAccountType; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java index 92843e4c764..a53e5eceb17 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java @@ -37,7 +37,6 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -74,7 +73,6 @@ public class SavingsAccountTransactionDataValidator { private final ConfigurationDomainService configurationDomainService; public void validateTransactionWithPivotDate(final LocalDate transactionDate, final SavingsAccount savingsAccount) { - final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig(); final boolean isRelaxingDaysConfigOn = this.configurationDomainService.isRelaxingDaysConfigForPivotDateEnabled(); @@ -85,14 +83,13 @@ public void validateTransactionWithPivotDate(final LocalDate transactionDate, fi if (isRelaxingDaysConfigOn) { pivotDate = pivotDate.minusDays(this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate()); } - if (pivotDate.isAfter(transactionDate)) { + if (DateUtils.isAfter(pivotDate, transactionDate)) { throw new TransactionBeforePivotDateNotAllowed(transactionDate, pivotDate); } } } public void validate(final JsonCommand command) { - final String json = command.json(); if (StringUtils.isBlank(json)) { @@ -287,7 +284,7 @@ public void validateHoldAndAssembleForm(final String json, final SavingsAccount } // compare two dates now - if (lastTransactionDate != null && transactionDate.isBefore(lastTransactionDate)) { + if (DateUtils.isBefore(transactionDate, lastTransactionDate)) { baseDataValidator.parameter(SavingsApiConstants.dateParamName).value(lastTransactionDate).failWithCode( "validation.msg.date.can.not.be.before.last.transaction.date", "Amount can be put on hold only after last transaction"); } @@ -295,8 +292,7 @@ public void validateHoldAndAssembleForm(final String json, final SavingsAccount throwExceptionIfValidationWarningsExist(dataValidationErrors); } - public SavingsAccountTransaction validateReleaseAmountAndAssembleForm(final SavingsAccountTransaction holdTransaction, - final AppUser createdUser) { + public SavingsAccountTransaction validateReleaseAmountAndAssembleForm(final SavingsAccountTransaction holdTransaction) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); @@ -317,10 +313,8 @@ public SavingsAccountTransaction validateReleaseAmountAndAssembleForm(final Savi } throwExceptionIfValidationWarningsExist(dataValidationErrors); - LocalDateTime createdDate = DateUtils.getLocalDateTimeOfSystem(); LocalDate transactionDate = DateUtils.getBusinessLocalDate(); - SavingsAccountTransaction transaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate, createdDate, - createdUser); + SavingsAccountTransaction transaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate); return transaction; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java index 2d5dd37e350..ad5e7726637 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java @@ -95,12 +95,14 @@ public class SavingsProductDataValidator { interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, SavingsApiConstants.withdrawalFeeAmountParamName, SavingsApiConstants.withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, - SavingsApiConstants.accountingRuleParamName, SavingsApiConstants.chargesParamName, SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), - SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), - SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), - SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), - SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), - SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(), + SavingsApiConstants.accountingRuleParamName, SavingsApiConstants.chargesParamName, + SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), + SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), + SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), + SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), + SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), + SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), + SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(), SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java index 317c6b03b07..af3b000cfa8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java @@ -69,7 +69,6 @@ import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.staff.domain.Staff; @@ -358,7 +357,7 @@ public SavingsAccount assembleFrom(final JsonCommand command, final AppUser subm if (account != null) { account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); - account.validateNewApplicationState(DateUtils.getBusinessLocalDate(), depositAccountType.resourceName()); + account.validateNewApplicationState(depositAccountType.resourceName()); } return account; @@ -448,7 +447,6 @@ public DepositAccountRecurringDetail assembleAccountRecurringDetail(final JsonCo public Collection assembleBulkMandatorySavingsAccountTransactionDTOs(final JsonCommand command, final PaymentDetail paymentDetail) { - AppUser user = getAppUserIfPresent(); final String json = command.json(); if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); @@ -478,7 +476,7 @@ public Collection assembleBulkMandatorySavingsAcco detail = this.paymentDetailAssembler.fetchPaymentDetail(savingsTransactionElement); } final SavingsAccountTransactionDTO savingsAccountTransactionDTO = new SavingsAccountTransactionDTO(formatter, - transactionDate, dueAmount, detail, DateUtils.getLocalDateTimeOfSystem(), savingsId, user, depositAccountType); + transactionDate, dueAmount, detail, savingsId, depositAccountType); savingsAccountTransactions.add(savingsAccountTransactionDTO); } } @@ -486,13 +484,4 @@ public Collection assembleBulkMandatorySavingsAcco return savingsAccountTransactions; } - - private AppUser getAppUserIfPresent() { - AppUser user = null; - if (this.context != null) { - user = this.context.getAuthenticatedUserIfPresent(); - } - return user; - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java index 34fe6d04ff1..3b29b900c14 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java @@ -42,19 +42,18 @@ SavingsAccountTransaction handleSavingDeposit(SavingsAccount account, DateTimeFo BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isRegularTransaction); Long handleFDAccountClosure(FixedDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command, - LocalDate tenantsTodayDate, Map changes); + Map changes); @Transactional - Long handleFDAccountMaturityClosure(FixedDepositAccount account, PaymentDetail paymentDetail, AppUser user, LocalDate tenantsTodayDate, - DateTimeFormatter fmt, LocalDate closedDate, Integer onAccountClosureId, Long toSavingsId, String transferDescription, - Map changes); + Long handleFDAccountMaturityClosure(FixedDepositAccount account, PaymentDetail paymentDetail, AppUser user, DateTimeFormatter fmt, + LocalDate closedDate, Integer onAccountClosureId, Long toSavingsId, String transferDescription, Map changes); Long handleRDAccountClosure(RecurringDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command, - LocalDate tenantsTodayDate, Map changes); + Map changes); Long handleFDAccountPreMatureClosure(FixedDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command, - LocalDate tenantsTodayDate, Map changes); + Map changes); Long handleRDAccountPreMatureClosure(RecurringDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command, - LocalDate tenantsTodayDate, Map changes); + Map changes); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java index 16e01e30c5a..52290bc4c42 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java @@ -40,8 +40,6 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.AccountTransferDTO; import org.apache.fineract.portfolio.account.domain.AccountTransferType; @@ -69,9 +67,7 @@ @Service public class DepositAccountDomainServiceJpa implements DepositAccountDomainService { - private final PlatformSecurityContext context; private final SavingsAccountRepositoryWrapper savingsAccountRepository; - private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final AccountNumberGenerator accountNumberGenerator; private final DepositAccountAssembler depositAccountAssembler; @@ -82,18 +78,14 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi private final CalendarInstanceRepository calendarInstanceRepository; @Autowired - public DepositAccountDomainServiceJpa(final PlatformSecurityContext context, - final SavingsAccountRepositoryWrapper savingsAccountRepository, - final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, + public DepositAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository, final JournalEntryWritePlatformService journalEntryWritePlatformService, final AccountNumberGenerator accountNumberGenerator, final DepositAccountAssembler depositAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService, final AccountTransfersWritePlatformService accountTransfersWritePlatformService, final ConfigurationDomainService configurationDomainService, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final CalendarInstanceRepository calendarInstanceRepository) { - this.context = context; this.savingsAccountRepository = savingsAccountRepository; - this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.accountNumberGenerator = accountNumberGenerator; this.depositAccountAssembler = depositAccountAssembler; @@ -135,7 +127,6 @@ public SavingsAccountTransaction handleFDDeposit(final FixedDepositAccount accou public SavingsAccountTransaction handleRDDeposit(final RecurringDepositAccount account, final DateTimeFormatter fmt, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final boolean isRegularTransaction) { - AppUser user = getAppUserIfPresent(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -151,7 +142,7 @@ public SavingsAccountTransaction handleRDDeposit(final RecurringDepositAccount a final boolean isAnyActivationChargesDue = isAnyActivationChargesDue(account); if (isAnyActivationChargesDue) { updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user); + account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); this.savingsAccountRepository.saveAndFlush(account); } account.handleScheduleInstallments(deposit); @@ -193,8 +184,7 @@ private boolean isAnyActivationChargesDue(final RecurringDepositAccount account) @Transactional @Override public Long handleFDAccountClosure(final FixedDepositAccount account, final PaymentDetail paymentDetail, final AppUser user, - final JsonCommand command, final LocalDate tenantsTodayDate, final Map changes) { - + final JsonCommand command, final Map changes) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -204,9 +194,7 @@ public Long handleFDAccountClosure(final FixedDepositAccount account, final Paym final boolean isPreMatureClosure = false; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); - /*** - * Update account transactionIds for post journal entries. - */ + // Update account transactionIds for post journal entries. updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); /* * final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, @@ -254,7 +242,7 @@ public Long handleFDAccountClosure(final FixedDepositAccount account, final Paym savingsTransactionId = withdrawal.getId(); } - account.close(user, command, tenantsTodayDate, changes); + account.close(user, command, changes); this.savingsAccountRepository.save(account); return savingsTransactionId; @@ -263,8 +251,8 @@ public Long handleFDAccountClosure(final FixedDepositAccount account, final Paym @Transactional @Override public Long handleFDAccountMaturityClosure(final FixedDepositAccount account, final PaymentDetail paymentDetail, final AppUser user, - final LocalDate tenantsTodayDate, final DateTimeFormatter fmt, final LocalDate closedDate, final Integer onAccountClosureId, - final Long toSavingsId, final String transferDescription, Map changes) { + final DateTimeFormatter fmt, final LocalDate closedDate, final Integer onAccountClosureId, final Long toSavingsId, + final String transferDescription, Map changes) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -329,7 +317,7 @@ public Long handleFDAccountMaturityClosure(final FixedDepositAccount account, fi } // if(!processMaturityInstructionOnly) - // account.close(user, command, tenantsTodayDate, changes); + // account.close(user, command, changes); this.savingsAccountRepository.save(account); @@ -341,7 +329,7 @@ public Long handleFDAccountMaturityClosure(final FixedDepositAccount account, fi @Transactional @Override public Long handleRDAccountClosure(final RecurringDepositAccount account, final PaymentDetail paymentDetail, final AppUser user, - final JsonCommand command, final LocalDate tenantsTodayDate, final Map changes) { + final JsonCommand command, final Map changes) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -352,9 +340,7 @@ public Long handleRDAccountClosure(final RecurringDepositAccount account, final final boolean isPreMatureClosure = false; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); - /*** - * Update account transactionIds for post journal entries. - */ + // Update account transactionIds for post journal entries. updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final MathContext mc = MathContext.DECIMAL64; @@ -383,7 +369,7 @@ public Long handleRDAccountClosure(final RecurringDepositAccount account, final Integer frequency = CalendarUtils.getInterval(calendar.getRecurrence()); frequency = frequency == -1 ? 1 : frequency; reinvestedDeposit.generateSchedule(frequencyType, frequency, calendar); - reinvestedDeposit.processAccountUponActivation(fmt, user, postReversals); + reinvestedDeposit.processAccountUponActivation(fmt, postReversals); reinvestedDeposit.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); this.savingsAccountRepository.save(reinvestedDeposit); @@ -411,7 +397,7 @@ public Long handleRDAccountClosure(final RecurringDepositAccount account, final savingsTransactionId = withdrawal.getId(); } - account.close(user, command, tenantsTodayDate, changes); + account.close(user, command, changes); this.savingsAccountRepository.save(account); @@ -456,7 +442,7 @@ private void autoGenerateAccountNumber(final SavingsAccount account) { @Transactional @Override public Long handleFDAccountPreMatureClosure(final FixedDepositAccount account, final PaymentDetail paymentDetail, final AppUser user, - final JsonCommand command, final LocalDate tenantsTodayDate, final Map changes) { + final JsonCommand command, final Map changes) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -467,9 +453,7 @@ public Long handleFDAccountPreMatureClosure(final FixedDepositAccount account, f final boolean isPreMatureClosure = true; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); - /*** - * Update account transactionIds for post journal entries. - */ + // Update account transactionIds for post journal entries. updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); @@ -502,7 +486,7 @@ public Long handleFDAccountPreMatureClosure(final FixedDepositAccount account, f savingsTransactionId = withdrawal.getId(); } - account.prematureClosure(user, command, tenantsTodayDate, changes); + account.prematureClosure(user, command, changes); this.savingsAccountRepository.save(account); @@ -513,7 +497,7 @@ public Long handleFDAccountPreMatureClosure(final FixedDepositAccount account, f @Transactional @Override public Long handleRDAccountPreMatureClosure(final RecurringDepositAccount account, final PaymentDetail paymentDetail, - final AppUser user, final JsonCommand command, final LocalDate tenantsTodayDate, final Map changes) { + final AppUser user, final JsonCommand command, final Map changes) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -558,7 +542,7 @@ public Long handleRDAccountPreMatureClosure(final RecurringDepositAccount accoun savingsTransactionId = withdrawal.getId(); } - account.prematureClosure(user, command, tenantsTodayDate, changes); + account.prematureClosure(user, command, changes); this.savingsAccountRepository.save(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); return savingsTransactionId; @@ -592,12 +576,4 @@ private void updateAlreadyPostedTransactions(final Set existingTransaction } } } - - private AppUser getAppUserIfPresent() { - AppUser user = null; - if (this.context != null) { - user = this.context.getAuthenticatedUserIfPresent(); - } - return user; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChart.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChart.java index 8f3d40b7ad5..36a2358aaf2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChart.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountInterestRateChart.java @@ -103,12 +103,12 @@ public DepositAccountInterestRateChartSlabs findChartSlab(Long chartSlabId) { return null; } - public LocalDate getFromDateAsLocalDate() { - return this.chartFields.getFromDateAsLocalDate(); + public LocalDate getFromDate() { + return this.chartFields.getFromDate(); } - public LocalDate getEndDateAsLocalDate() { - return this.chartFields.getEndDateAsLocalDate(); + public LocalDate getEndDate() { + return this.chartFields.getEndDate(); } public Long savingsAccountId() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransaction.java index 029c2500c93..78a5f2f8062 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountOnHoldTransaction.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.savings.domain; +import static org.apache.fineract.infrastructure.core.service.DateUtils.getSystemZoneId; + import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -28,8 +30,9 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; -import org.apache.fineract.infrastructure.core.service.DateUtils; +import java.time.OffsetDateTime; +import java.util.Optional; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransaction; @@ -37,7 +40,7 @@ @Entity @Table(name = "m_deposit_account_on_hold_transaction") -public class DepositAccountOnHoldTransaction extends AbstractPersistableCustom { +public class DepositAccountOnHoldTransaction extends AbstractAuditableWithUTCDateTimeCustom { @ManyToOne @JoinColumn(name = "savings_account_id", nullable = true) @@ -55,8 +58,9 @@ public class DepositAccountOnHoldTransaction extends AbstractPersistableCustom { @Column(name = "is_reversed", nullable = false) private boolean reversed; - @Column(name = "created_date", nullable = false) - private LocalDateTime createdDate; + @Deprecated + @Column(name = "created_date", nullable = true) + private LocalDateTime createdDateToRemove; @OneToOne(cascade = CascadeType.ALL, mappedBy = "depositAccountOnHoldTransaction", optional = true, orphanRemoval = true) private GuarantorFundingTransaction guarantorFundingTransaction; @@ -69,7 +73,7 @@ private DepositAccountOnHoldTransaction(final SavingsAccount savingsAccount, fin this.amount = amount; this.transactionType = transactionType.getValue(); this.transactionDate = transactionDate; - this.createdDate = DateUtils.getLocalDateTimeOfSystem(); + this.createdDateToRemove = null; // #audit backward compatibility deprecated this.reversed = reversed; } @@ -87,14 +91,30 @@ public static DepositAccountOnHoldTransaction release(final SavingsAccount savin reversed); } + public SavingsAccount getSavingsAccount() { + return savingsAccount; + } + public BigDecimal getAmount() { return this.amount; } - public Money getAmountMoney(final MonetaryCurrency currency) { + public Money getAmount(final MonetaryCurrency currency) { return Money.of(currency, this.amount); } + public DepositAccountOnHoldTransactionType getTransactionType() { + return DepositAccountOnHoldTransactionType.fromInt(this.transactionType); + } + + public LocalDate getTransactionDate() { + return this.transactionDate; + } + + public MonetaryCurrency getCurrency() { + return getSavingsAccount().getCurrency(); + } + public void reverseTransaction() { this.reversed = true; if (this.getTransactionType().isHold()) { @@ -104,12 +124,10 @@ public void reverseTransaction() { } } - public DepositAccountOnHoldTransactionType getTransactionType() { - return DepositAccountOnHoldTransactionType.fromInt(this.transactionType); - } - - public LocalDate getTransactionDate() { - return this.transactionDate; + @Override + public Optional getCreatedDate() { + // #audit backward compatibility keep system datetime + return Optional.ofNullable(super.getCreatedDate() + .orElse(createdDateToRemove == null ? null : createdDateToRemove.atZone(getSystemZoneId()).toOffsetDateTime())); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java index 936e38a7cde..64553b3b406 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java @@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; @@ -223,7 +224,7 @@ public void updateDepositAmount(final BigDecimal depositAmount) { this.depositAmount = depositAmount; } - public LocalDate getMaturityLocalDate() { + public LocalDate getMaturityDate() { return this.maturityDate; } @@ -246,7 +247,7 @@ public Integer getActualDepositPeriod(final LocalDate interestPostingUpToDate, f } Integer actualDepositPeriod = this.depositPeriod; - if (depositFromDate == null || getMaturityLocalDate() == null || interestPostingUpToDate.isEqual(getMaturityLocalDate())) { + if (depositFromDate == null || getMaturityDate() == null || DateUtils.isEqual(interestPostingUpToDate, getMaturityDate())) { return actualDepositPeriod; } @@ -315,11 +316,7 @@ public boolean isTransferInterestToLinkedAccount() { } public boolean isAfterExpectedFirstDepositDate(final LocalDate compareDate) { - boolean isAfterExpectedFirstDepositDate = false; - if (this.expectedFirstDepositOnDate != null) { - isAfterExpectedFirstDepositDate = compareDate.isAfter(getExpectedFirstDepositOnDate()); - } - return isAfterExpectedFirstDepositDate; + return this.expectedFirstDepositOnDate != null && DateUtils.isAfter(compareDate, getExpectedFirstDepositOnDate()); } public Integer getOnAccountClosureType() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index 370837d9342..d92280acce1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -197,18 +197,8 @@ public void updateMaturityDateAndAmountBeforeAccountActivation(final MathContext String refNo = null; final Money transactionAmountMoney = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, - this.accountSubmittedOrActivationDate(), transactionAmountMoney, DateUtils.getLocalDateTimeOfSystem(), null, refNo); // TODO: - // verify - // if - // it - // is - // ok - // to - // pass - // null - // for - // AppUser - transaction.updateRunningBalance(transactionAmountMoney); + this.accountSubmittedOrActivationDate(), transactionAmountMoney, refNo); + transaction.setRunningBalance(transactionAmountMoney); transaction.updateCumulativeBalanceAndDates(this.getCurrency(), interestCalculatedUpto()); allTransactions.add(transaction); updateMaturityDateAndAmount(mc, allTransactions, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -260,8 +250,7 @@ public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrent } } - final LocalDate todayDate = DateUtils.getBusinessLocalDate(); - if (!this.maturityDate().isAfter(todayDate)) { + if (!DateUtils.isDateInTheFuture(maturityDate())) { // update account status this.status = SavingsAccountStatusType.MATURED.getValue(); postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); @@ -338,14 +327,12 @@ private List calculateInterestPayable(final MathContext mc, final } this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods); - this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilLocalDate(), + this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, getLockedInUntilDate(), isTransferInterestToOtherAccount()); return allPostingPeriods; } - public void prematureClosure(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, - final Map actualChanges) { - + public void prematureClosure(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction); @@ -362,14 +349,13 @@ public void prematureClosure(final AppUser currentUser, final JsonCommand comman final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - if (closedDate.isBefore(getActivationLocalDate())) { + if (DateUtils.isBefore(closedDate, getActivationDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.activation.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (isAccountLocked(closedDate)) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.lockin.period"); @@ -377,16 +363,14 @@ public void prematureClosure(final AppUser currentUser, final JsonCommand comman throw new PlatformApiDataValidationException(dataValidationErrors); } } - - if (closedDate.isAfter(maturityDate())) { + if (maturityDate() != null && DateUtils.isAfter(closedDate, maturityDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.before.maturity.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - - if (closedDate.isAfter(tenantsTodayDate)) { + if (DateUtils.isAfterBusinessDate(closedDate)) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("cannot.be.a.future.date"); if (!dataValidationErrors.isEmpty()) { @@ -437,9 +421,7 @@ public Money activateWithBalance() { return Money.of(this.currency, this.accountTermAndPreClosure.depositAmount()); } - public void close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, - final Map actualChanges) { - + public void close(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); @@ -456,21 +438,21 @@ public void close(final AppUser currentUser, final JsonCommand command, final Lo final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - if (closedDate.isBefore(getActivationLocalDate())) { + if (DateUtils.isBefore(closedDate, getActivationDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.activation.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (closedDate.isBefore(maturityDate())) { + if (DateUtils.isBefore(closedDate, maturityDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.account.maturity.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (closedDate.isAfter(tenantsTodayDate)) { + if (DateUtils.isAfterBusinessDate(closedDate)) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("cannot.be.a.future.date"); if (!dataValidationErrors.isEmpty()) { @@ -548,7 +530,8 @@ public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrent LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); - interestPostingTransactionDate = interestPostingTransactionDate.isAfter(interestPostingUpToDate) ? interestPostingUpToDate + interestPostingTransactionDate = DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate) + ? interestPostingUpToDate : interestPostingTransactionDate; final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); @@ -648,16 +631,14 @@ private Money calculatePreMatureInterest(final LocalDate preMatureDate, final Li return interestOnMaturity; } - /* - public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - boolean postReversals = false; - super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); - } - */ + /* + * public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, final + * boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final LocalDate + * postInterestOnDate, final boolean backdatedTxnsAllowedTill) { final LocalDate interestPostingUpToDate = + * interestPostingUpToDate(postingDate); boolean postReversals = false; super.postInterest(mc, + * interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + * financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } + */ @Override public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, @@ -671,14 +652,14 @@ public List calculateInterestUsing(final MathContext mc, final Lo private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { LocalDate interestPostingUpToDate = interestPostingDate; final LocalDate uptoMaturityDate = interestCalculatedUpto(); - if (uptoMaturityDate != null && uptoMaturityDate.isBefore(interestPostingDate)) { + if (uptoMaturityDate != null && DateUtils.isBefore(uptoMaturityDate, interestPostingDate)) { interestPostingUpToDate = uptoMaturityDate; } return interestPostingUpToDate; } public LocalDate maturityDate() { - return this.accountTermAndPreClosure.getMaturityLocalDate(); + return this.accountTermAndPreClosure.getMaturityDate(); } public BigDecimal maturityAmount() { @@ -697,8 +678,8 @@ private Money totalInterestPosted() { } @Override - public Map activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { - final Map actualChanges = super.activate(currentUser, command, tenantsTodayDate); + public Map activate(final AppUser currentUser, final JsonCommand command) { + final Map actualChanges = super.activate(currentUser, command); // if (isAccountLocked(calculateMaturityDate())) { // final List dataValidationErrors = new @@ -777,8 +758,8 @@ private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { } if (this.chart != null) { - final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate(); - LocalDate chartEndDate = this.chart.getEndDateAsLocalDate(); + final LocalDate chartFromDate = this.chart.getFromDate(); + LocalDate chartEndDate = this.chart.getEndDate(); chartEndDate = chartEndDate == null ? DateUtils.getBusinessLocalDate() : chartEndDate; final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate); @@ -790,7 +771,7 @@ private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), calculateMaturityDate(), this.client); - if (applicableInterestRate.compareTo(BigDecimal.ZERO) == 0 ? Boolean.TRUE : Boolean.FALSE) { + if (applicableInterestRate.compareTo(BigDecimal.ZERO) == 0) { baseDataValidator.reset() .failWithCodeNoParameterAddedToErrorCode("no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java index ee918cb571d..07d956a3c6c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java @@ -258,8 +258,8 @@ public void validateChart(final DataValidatorBuilder baseDataValidator, final In if (!existingChart.equals(comparingChart)) { if (existingChart.chartFields().isOverlapping(comparingChart.chartFields())) { baseDataValidator.failWithCodeNoParameterAddedToErrorCode("chart.overlapping.from.and.end.dates", - existingChart.getFromDateAsLocalDate(), existingChart.getEndDateAsLocalDate(), - comparingChart.getFromDateAsLocalDate(), comparingChart.getEndDateAsLocalDate()); + existingChart.getFromDate(), existingChart.getEndDate(), comparingChart.getFromDate(), + comparingChart.getEndDate()); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java index f34c9407bf3..349f1eb6009 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java @@ -286,7 +286,7 @@ public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrent } final LocalDate todayDate = DateUtils.getBusinessLocalDate(); - if (!this.maturityDate().isAfter(todayDate)) { + if (!DateUtils.isAfter(this.maturityDate(), todayDate)) { // update account status this.status = SavingsAccountStatusType.MATURED.getValue(); postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, todayDate, postReversals); @@ -368,7 +368,7 @@ private List calculateInterestPayable(final MathContext mc, final allPostingPeriods.add(postingPeriod); } - this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilLocalDate(), + this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilDate(), isTransferInterestToOtherAccount()); // this.summary.updateFromInterestPeriodSummaries(this.currency, // allPostingPeriods); @@ -381,8 +381,8 @@ private List getTransactions(final LocalDate depositE allTransactions.addAll(retreiveOrderedNonInterestPostingTransactions()); LocalDate latestTransactionDate = null; for (final SavingsAccountTransaction installment : allTransactions) { - if (latestTransactionDate == null || latestTransactionDate.isBefore(installment.getTransactionLocalDate())) { - latestTransactionDate = installment.getTransactionLocalDate(); + if (latestTransactionDate == null || DateUtils.isBefore(latestTransactionDate, installment.getTransactionDate())) { + latestTransactionDate = installment.getTransactionDate(); } } String refNo = null; @@ -390,11 +390,11 @@ private List getTransactions(final LocalDate depositE for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) { if (installment.isPrincipalNotCompleted(getCurrency())) { LocalDate dueDate = installment.dueDate(); - if (latestTransactionDate != null && dueDate.isBefore(latestTransactionDate)) { + if (DateUtils.isBefore(dueDate, latestTransactionDate)) { dueDate = latestTransactionDate; } final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, dueDate, - installment.getDepositAmountOutstanding(getCurrency()), DateUtils.getLocalDateTimeOfSystem(), null, refNo); + installment.getDepositAmountOutstanding(getCurrency()), refNo); allTransactions.add(transaction); } } @@ -414,7 +414,7 @@ private List getTransactions(final LocalDate depositE } runningBalance = runningBalance.plus(transactionAmount); - transaction.updateRunningBalance(runningBalance); + transaction.setRunningBalance(runningBalance); } } // loop over transactions in reverse @@ -423,7 +423,7 @@ private List getTransactions(final LocalDate depositE final SavingsAccountTransaction transaction = allTransactions.get(i); if (transaction.isNotReversed() && !transaction.isInterestPostingAndNotReversed()) { transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); - endOfBalanceDate = transaction.transactionLocalDate().minusDays(1); + endOfBalanceDate = transaction.getTransactionDate().minusDays(1); } } return allTransactions; @@ -437,9 +437,7 @@ public LocalDate depositStartDate() { return depositStartDate; } - public void prematureClosure(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, - final Map actualChanges) { - + public void prematureClosure(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction); @@ -456,14 +454,13 @@ public void prematureClosure(final AppUser currentUser, final JsonCommand comman final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - if (closedDate.isBefore(getActivationLocalDate())) { + if (DateUtils.isBefore(closedDate, getActivationDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.activation.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (isAccountLocked(closedDate)) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.lockin.period"); @@ -471,16 +468,14 @@ public void prematureClosure(final AppUser currentUser, final JsonCommand comman throw new PlatformApiDataValidationException(dataValidationErrors); } } - - if (closedDate.isAfter(maturityDate())) { + if (maturityDate() != null && DateUtils.isAfter(closedDate, maturityDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.before.maturity.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - - if (closedDate.isAfter(tenantsTodayDate)) { + if (DateUtils.isAfterBusinessDate(closedDate)) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("cannot.be.a.future.date"); if (!dataValidationErrors.isEmpty()) { @@ -541,14 +536,14 @@ public Money activateWithBalance() { return Money.of(this.currency, this.minRequiredOpeningBalance); } - protected void processAccountUponActivation(final DateTimeFormatter fmt, final AppUser user, final boolean postReversals) { + protected void processAccountUponActivation(final DateTimeFormatter fmt, final boolean postReversals) { final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); final boolean backdatedTxnsAllowedTill = false; String refNo = null; final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); if (minRequiredOpeningBalance.isGreaterThanZero()) { - final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(), - minRequiredOpeningBalance.getAmount(), null, DateUtils.getLocalDateTimeOfSystem(), user, accountType); + final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationDate(), + minRequiredOpeningBalance.getAmount(), null, null, accountType); deposit(transactionDTO, backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo); // update existing transactions so derived balance fields are @@ -557,9 +552,7 @@ protected void processAccountUponActivation(final DateTimeFormatter fmt, final A } } - public void close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, - final Map actualChanges) { - + public void close(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); @@ -576,21 +569,21 @@ public void close(final AppUser currentUser, final JsonCommand command, final Lo final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - if (closedDate.isBefore(getActivationLocalDate())) { + if (DateUtils.isBefore(closedDate, getActivationDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.activation.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (maturityDate() != null && closedDate.isBefore(maturityDate())) { + if (maturityDate() != null && DateUtils.isBefore(closedDate, maturityDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.account.maturity.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (closedDate.isAfter(tenantsTodayDate)) { + if (DateUtils.isAfterBusinessDate(closedDate)) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("cannot.be.a.future.date"); if (!dataValidationErrors.isEmpty()) { @@ -659,7 +652,8 @@ public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrent for (final PostingPeriod interestPostingPeriod : postingPeriods) { LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); - interestPostingTransactionDate = interestPostingTransactionDate.isAfter(interestPostingUpToDate) ? interestPostingUpToDate + interestPostingTransactionDate = DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate) + ? interestPostingUpToDate : interestPostingTransactionDate; final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); @@ -756,16 +750,14 @@ private Money calculatePreMatureInterest(final LocalDate preMatureDate, final Li return interestOnMaturity; } - /* - @Override - public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postInterestAson, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestAson, backdatedTxnsAllowedTill, postReversals); - } - */ + /* + * @Override public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean + * isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer + * financialYearBeginningMonth, final LocalDate postInterestAson, final boolean backdatedTxnsAllowedTill, final + * boolean postReversals) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + * super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + * financialYearBeginningMonth, postInterestAson, backdatedTxnsAllowedTill, postReversals); } + */ @Override public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, @@ -779,14 +771,14 @@ public List calculateInterestUsing(final MathContext mc, final Lo private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { LocalDate interestPostingUpToDate = interestPostingDate; final LocalDate uptoMaturityDate = interestCalculatedUpto(); - if (uptoMaturityDate != null && uptoMaturityDate.isBefore(interestPostingDate)) { + if (uptoMaturityDate != null && DateUtils.isBefore(uptoMaturityDate, interestPostingDate)) { interestPostingUpToDate = uptoMaturityDate; } return interestPostingUpToDate; } public LocalDate maturityDate() { - return this.accountTermAndPreClosure.getMaturityLocalDate(); + return this.accountTermAndPreClosure.getMaturityDate(); } public BigDecimal maturityAmount() { @@ -815,11 +807,10 @@ private Money totalInterestPosted() { } @Override - public Map activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { - - final Map actualChanges = super.activate(currentUser, command, tenantsTodayDate); + public Map activate(final AppUser currentUser, final JsonCommand command) { + final Map actualChanges = super.activate(currentUser, command); - if (accountTermAndPreClosure.isAfterExpectedFirstDepositDate(getActivationLocalDate())) { + if (accountTermAndPreClosure.isAfterExpectedFirstDepositDate(getActivationDate())) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); @@ -891,13 +882,12 @@ public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO tran } public void handleScheduleInstallments(final SavingsAccountTransaction transaction) { - - final LocalDate transactionDate = transaction.transactionLocalDate(); + final LocalDate transactionDate = transaction.getTransactionDate(); Money transactionAmountUnprocessed = transaction.getAmount(getCurrency()); for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { if (currentInstallment.isNotFullyPaidOff() && transactionAmountUnprocessed.isGreaterThanZero()) { - if (!this.adjustAdvanceTowardsFuturePayments() && currentInstallment.dueDate().isAfter(transactionDate)) { + if (!this.adjustAdvanceTowardsFuturePayments() && DateUtils.isBefore(transactionDate, currentInstallment.dueDate())) { transactionAmountUnprocessed = Money.zero(getCurrency()); } transactionAmountUnprocessed = handleInstallmentTransaction(currentInstallment, transactionAmountUnprocessed, @@ -908,7 +898,6 @@ public void handleScheduleInstallments(final SavingsAccountTransaction transacti } public void updateScheduleInstallments() { - // reset all installments to process from the beginning for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { currentInstallment.resetDerivedFields(); @@ -925,8 +914,7 @@ public void updateScheduleInstallmentsWithNewRecommendedDepositAmount(BigDecimal // reset all installments to process from the beginning, also update // deposit amount as necessary for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { - if (currentInstallment.dueDate().isAfter(depositAmountupdatedFromDate) - || currentInstallment.dueDate().isEqual(depositAmountupdatedFromDate)) { + if (!DateUtils.isAfter(depositAmountupdatedFromDate, currentInstallment.dueDate())) { currentInstallment.updateDepositAmountAndResetDerivedFields(newDepositAmount); } else { currentInstallment.resetDerivedFields(); @@ -961,19 +949,14 @@ private List retreiveOrderedDepositTransactions() { */ protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, final List installments, final LocalDate transactionDate) { - final RecurringDepositScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); - - return transactionDate.isBefore(currentInstallment.dueDate()); + return DateUtils.isBefore(transactionDate, currentInstallment.dueDate()); } private Money handleInstallmentTransaction(final RecurringDepositScheduleInstallment currentInstallment, final Money transactionAmountUnprocessed, final LocalDate transactionDate) { - Money transactionAmountRemaining = transactionAmountUnprocessed; - Money depositAmountPortion = Money.zero(transactionAmountRemaining.getCurrency()); - - depositAmountPortion = currentInstallment.payInstallment(transactionDate, transactionAmountRemaining); + Money depositAmountPortion = currentInstallment.payInstallment(transactionDate, transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(depositAmountPortion); return transactionAmountRemaining; @@ -986,11 +969,11 @@ private boolean isAccountMatured() { private boolean isBeforeMaturityDate(final LocalDate compareDate) { final LocalDate maturityDate = this.maturityDate(); - return maturityDate == null ? true : compareDate.isBefore(maturityDate); + return maturityDate == null || DateUtils.isBefore(compareDate, maturityDate); } private boolean isBeforeDepositStartDate(LocalDate compareDate) { - return compareDate.isBefore(depositStartDate()); + return DateUtils.isBefore(compareDate, depositStartDate()); } public void validateDomainRules() { @@ -1005,7 +988,6 @@ public void validateDomainRules() { } private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { - final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail() .isMinDepositTermGreaterThanMaxDepositTerm(); // deposit period should be within min and max deposit term @@ -1065,8 +1047,8 @@ public void validateApplicableInterestRate() { .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); LocalDate maturityDate = calculateMaturityDate(); if (this.chart != null) { - final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate(); - LocalDate chartEndDate = this.chart.getEndDateAsLocalDate(); + final LocalDate chartFromDate = this.chart.getFromDate(); + LocalDate chartEndDate = this.chart.getEndDate(); chartEndDate = chartEndDate == null ? DateUtils.getBusinessLocalDate() : chartEndDate; final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate); @@ -1082,7 +1064,7 @@ public void validateApplicableInterestRate() { BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(maturityAmount, depositStartDate(), maturityDate, this.client); - if (applicableInterestRate.compareTo(BigDecimal.ZERO) == 0 ? Boolean.TRUE : Boolean.FALSE) { + if (applicableInterestRate.compareTo(BigDecimal.ZERO) == 0) { baseDataValidator.reset() .failWithCodeNoParameterAddedToErrorCode("no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); } @@ -1094,13 +1076,13 @@ public void validateApplicableInterestRate() { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - /** + /* * final boolean recurringFrequencyBeforeDepositPeriod = recurringFrequencyBeforeDepositPeriod(); * * if (!recurringFrequencyBeforeDepositPeriod) { * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( * "recurring.frequency.not.before.deposit.period"); } - **/ + */ } public boolean isReinvestOnClosure() { @@ -1153,10 +1135,8 @@ accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRat private boolean firstDepositDateBeforeAccountSubmittedOrActivationDate() { final LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); - if (expectedFirstDepositLocalDate == null) { - return false; - } - return expectedFirstDepositLocalDate.isBefore(accountSubmittedOrActivationDate()); + return expectedFirstDepositLocalDate != null + && DateUtils.isBefore(expectedFirstDepositLocalDate, accountSubmittedOrActivationDate()); } public void setDatesFrom(final LocalDate now) { @@ -1194,7 +1174,7 @@ public void generateSchedule(final PeriodFrequencyType frequency, final Integer int installmentNumber = 1; final LocalDate maturityDate = calcualteScheduleTillDate(frequency, recurringEvery); final BigDecimal depositAmount = this.recurringDetail.mandatoryRecommendedDepositAmount(); - while (maturityDate.isAfter(installmentDate)) { + while (DateUtils.isBefore(installmentDate, maturityDate)) { final RecurringDepositScheduleInstallment installment = RecurringDepositScheduleInstallment.installment(this, installmentNumber, installmentDate, depositAmount); addDepositScheduleInstallment(installment); @@ -1228,7 +1208,7 @@ public boolean isCalendarInherited() { public void updateOverduePayments(final LocalDate todayDate) { LocalDate overdueUptoDate = this.maturityDate(); - if (overdueUptoDate == null || overdueUptoDate.isAfter(todayDate)) { + if (overdueUptoDate == null || DateUtils.isAfter(overdueUptoDate, todayDate)) { overdueUptoDate = todayDate; } @@ -1236,7 +1216,7 @@ public void updateOverduePayments(final LocalDate todayDate) { int noOfOverdueInstallments = 0; Money totalOverdueAmount = Money.zero(getCurrency()); for (RecurringDepositScheduleInstallment installment : installments) { - if (installment.isNotFullyPaidOff() && overdueUptoDate.isAfter(installment.dueDate())) { + if (installment.isNotFullyPaidOff() && DateUtils.isAfter(overdueUptoDate, installment.dueDate())) { noOfOverdueInstallments++; totalOverdueAmount = totalOverdueAmount.plus(installment.getDepositAmountOutstanding(getCurrency())); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositScheduleInstallment.java index b332a4b2e8d..eef970f8dfb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositScheduleInstallment.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositScheduleInstallment.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.savings.domain; +import static org.apache.fineract.infrastructure.core.service.DateUtils.getSystemZoneId; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; @@ -25,13 +27,17 @@ import jakarta.persistence.Table; import java.math.BigDecimal; import java.time.LocalDate; -import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.Optional; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; @Entity @Table(name = "m_mandatory_savings_schedule") -public class RecurringDepositScheduleInstallment extends AbstractAuditableCustom { +public class RecurringDepositScheduleInstallment extends AbstractAuditableWithUTCDateTimeCustom { @ManyToOne(optional = false) @JoinColumn(name = "savings_account_id") @@ -64,15 +70,18 @@ public class RecurringDepositScheduleInstallment extends AbstractAuditableCustom @Column(name = "obligations_met_on_date") private LocalDate obligationsMetOnDate; + @Deprecated + @Column(name = "created_date") + private LocalDateTime createdDateToRemove; + + @Deprecated + @Column(name = "lastmodified_date") + private LocalDateTime lastModifiedDateToRemove; + /** * */ - protected RecurringDepositScheduleInstallment() { - this.installmentNumber = null; - this.fromDate = null; - this.dueDate = null; - this.obligationsMet = false; - } + protected RecurringDepositScheduleInstallment() {} /** * @param account @@ -165,6 +174,24 @@ public LocalDate dueDate() { return this.dueDate; } + public Integer installmentNumber() { + return this.installmentNumber; + } + + @Override + public Optional getCreatedDate() { + // #audit backward compatibility keep system datetime + return Optional.ofNullable(super.getCreatedDate() + .orElse(createdDateToRemove == null ? null : createdDateToRemove.atZone(getSystemZoneId()).toOffsetDateTime())); + } + + @Override + public Optional getLastModifiedDate() { + // #audit backward compatibility keep system datetime + return Optional.ofNullable(super.getLastModifiedDate() + .orElse(lastModifiedDateToRemove == null ? null : lastModifiedDateToRemove.atZone(getSystemZoneId()).toOffsetDateTime())); + } + public Money payInstallment(final LocalDate transactionDate, final Money transactionAmountRemaining) { final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); @@ -209,11 +236,11 @@ private void trackAdvanceAndLateTotalsForInstallment(final LocalDate transaction } private boolean isInAdvance(final LocalDate transactionDate) { - return transactionDate.isBefore(dueDate()); + return DateUtils.isBefore(transactionDate, dueDate()); } private boolean isLatePayment(final LocalDate transactionDate) { - return transactionDate.isAfter(dueDate()); + return DateUtils.isAfter(transactionDate, dueDate()); } private Money asMoney(final BigDecimal decimal, final MonetaryCurrency currency) { @@ -232,8 +259,4 @@ public void updateDepositAmountAndResetDerivedFields(BigDecimal newDepositAmount this.depositAmount = newDepositAmount; this.resetDerivedFields(); } - - public Integer installmentNumber() { - return this.installmentNumber; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 66ebffd6e90..42ed2c6acc9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -76,7 +76,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; @@ -132,7 +132,7 @@ @DiscriminatorColumn(name = "deposit_type_enum", discriminatorType = DiscriminatorType.INTEGER) @DiscriminatorValue("100") @SuppressWarnings({ "MemberName" }) -public class SavingsAccount extends AbstractPersistableCustom { +public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom { private static final Logger LOG = LoggerFactory.getLogger(SavingsAccount.class); @@ -589,19 +589,19 @@ protected boolean createWithHoldTransaction(final BigDecimal amount, final Local protected boolean updateWithHoldTransaction(final BigDecimal amount, final SavingsAccountTransaction withholdTransaction) { boolean isTaxAdded = false; if (this.taxGroup != null && amount.compareTo(BigDecimal.ZERO) > 0) { - Map taxSplit = TaxUtils.splitTax(amount, withholdTransaction.transactionLocalDate(), + Map taxSplit = TaxUtils.splitTax(amount, withholdTransaction.getTransactionDate(), this.taxGroup.getTaxGroupMappings(), amount.scale()); BigDecimal totalTax = TaxUtils.totalTaxAmount(taxSplit); if (totalTax.compareTo(BigDecimal.ZERO) > 0) { if (withholdTransaction.getId() == null) { - withholdTransaction.updateAmount(Money.of(currency, totalTax)); + withholdTransaction.setAmount(Money.of(currency, totalTax)); withholdTransaction.getTaxDetails().clear(); SavingsAccountTransaction.updateTaxDetails(taxSplit, withholdTransaction); isTaxAdded = true; } else if (totalTax.compareTo(withholdTransaction.getAmount()) != 0) { withholdTransaction.reverse(); SavingsAccountTransaction newWithholdTransaction = SavingsAccountTransaction.withHoldTax(this, office(), - withholdTransaction.transactionLocalDate(), Money.of(currency, totalTax), taxSplit); + withholdTransaction.getTransactionDate(), Money.of(currency, totalTax), taxSplit); addTransaction(newWithholdTransaction); isTaxAdded = true; } @@ -641,7 +641,7 @@ public List getManualPostingDates() { List transactions = new ArrayList<>(); for (SavingsAccountTransaction trans : this.transactions) { if (trans.isInterestPosting() && trans.isNotReversed() && !trans.isReversalTransaction() && trans.isManualTransaction()) { - transactions.add(trans.getTransactionLocalDate()); + transactions.add(trans.getTransactionDate()); } } return transactions; @@ -651,7 +651,7 @@ public List getManualPostingDatesWithPivotConfig() { List transactions = new ArrayList<>(); for (SavingsAccountTransaction trans : this.savingsAccountTransactions) { if (trans.isInterestPosting() && trans.isNotReversed() && trans.isManualTransaction()) { - transactions.add(trans.getTransactionLocalDate()); + transactions.add(trans.getTransactionDate()); } } return transactions; @@ -720,7 +720,7 @@ public List calculateInterestUsing(final MathContext mc, final Lo final List allPostingPeriods = new ArrayList<>(); Money periodStartingBalance; - if (this.startInterestCalculationDate != null && !this.getStartInterestCalculationDate().equals(this.getActivationLocalDate())) { + if (this.startInterestCalculationDate != null && !this.getStartInterestCalculationDate().equals(this.getActivationDate())) { LocalDate startInterestCalculationDate = this.startInterestCalculationDate; SavingsAccountTransaction transaction = null; if (backdatedTxnsAllowedTill) { @@ -774,7 +774,7 @@ public List calculateInterestUsing(final MathContext mc, final Lo allPostingPeriods.add(postingPeriod); } - this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, getLockedInUntilLocalDate(), + this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, getLockedInUntilDate(), isTransferInterestToOtherAccount()); this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods); @@ -882,15 +882,15 @@ protected void recalculateDailyBalances(final Money openingAccountBalance, final } runningBalance = runningBalance.plus(transactionAmount); - if (!transaction.getRunningBalance(transactionAmount.getCurrency()).isEqualTo(transactionAmount)) { - transaction.updateRunningBalance(runningBalance); + if (!transaction.getRunningBalance(this.currency).isEqualTo(transactionAmount)) { + transaction.setRunningBalance(runningBalance); } if (overdraftAmount.isZero() && runningBalance.isLessThanZero() && !transaction.isAmountOnHold()) { overdraftAmount = overdraftAmount.plus(runningBalance.getAmount().negate()); } if (transaction.getId() == null && overdraftAmount.isGreaterThanZero()) { - transaction.updateOverdraftAmount(overdraftAmount.getAmount()); - } else if (overdraftAmount.isNotEqualTo(transaction.getOverdraftAmount(getCurrency()))) { + transaction.setOverdraftAmount(overdraftAmount); + } else if (overdraftAmount.isNotEqualTo(transaction.getOverdraftAmount(this.currency))) { SavingsAccountTransaction accountTransaction = SavingsAccountTransaction.copyTransaction(transaction); if (transaction.isChargeTransaction()) { Set chargesPaidBy = transaction.getSavingsAccountChargesPaid(); @@ -905,9 +905,9 @@ protected void recalculateDailyBalances(final Money openingAccountBalance, final reversal = SavingsAccountTransaction.reversal(transaction); } if (overdraftAmount.isGreaterThanZero()) { - accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount()); + accountTransaction.setOverdraftAmount(overdraftAmount); } - accountTransaction.updateRunningBalance(runningBalance); + accountTransaction.setRunningBalance(runningBalance); if (backdatedTxnsAllowedTill) { addTransactionToExisting(accountTransaction); if (reversal != null) { @@ -946,7 +946,7 @@ protected void resetAccountTransactionsEndOfDayBalances(final List dataValidationErrors = new ArrayList<>(); dataValidationErrors.add(error); @@ -1143,14 +1141,13 @@ public SavingsAccountTransaction withdraw(final SavingsAccountTransactionDTO tra if (applyWithdrawFee) { // auto pay withdrawal fee - payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser(), - transactionDTO.getPaymentDetail(), backdatedTxnsAllowedTill, refNo); + payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getPaymentDetail(), + backdatedTxnsAllowedTill, refNo); } final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(), - transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney, - transactionDTO.getCreatedDate(), transactionDTO.getAppUser(), refNo); + transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney, refNo); if (backdatedTxnsAllowedTill) { addTransactionToExisting(transaction); @@ -1181,10 +1178,9 @@ public BigDecimal calculateWithdrawalFee(final BigDecimal transactionAmount) { return result; } - private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDate transactionDate, final AppUser user, - final PaymentDetail paymentDetail, final boolean backdatedTxnsAllowedTill, final String refNo) { + private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDate transactionDate, final PaymentDetail paymentDetail, + final boolean backdatedTxnsAllowedTill, final String refNo) { for (SavingsAccountCharge charge : this.charges()) { - if (charge.isWithdrawalFee() && charge.isActive()) { if (charge.getFreeWithdrawalCount() == null) { @@ -1194,32 +1190,30 @@ private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDat if (charge.isEnablePaymentType() && charge.isEnableFreeWithdrawal()) { // discount transaction to // specific paymentType if (paymentDetail.getPaymentType().getName().equals(charge.getCharge().getPaymentType().getName())) { - resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, user, refNo); + resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, refNo); } } else if (charge.isEnablePaymentType()) { // normal charge-transaction to specific paymentType if (paymentDetail.getPaymentType().getName().equals(charge.getCharge().getPaymentType().getName())) { charge.updateWithdralFeeAmount(transactionAmount); - this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, - backdatedTxnsAllowedTill, refNo); + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, backdatedTxnsAllowedTill, + refNo); } } else if (!charge.isEnablePaymentType() && charge.isEnableFreeWithdrawal()) { // discount transaction // irrespective of // PaymentTypes. - resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, user, refNo); + resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, refNo); } else { // normal-withdraw charge.updateWithdralFeeAmount(transactionAmount); - this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, backdatedTxnsAllowedTill, + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, backdatedTxnsAllowedTill, refNo); } - } - } } private void resetFreeChargeDaysCount(SavingsAccountCharge charge, final BigDecimal transactionAmount, final LocalDate transactionDate, - final AppUser user, final String refNo) { + final String refNo) { LocalDate resetDate = charge.getResetChargeDate(); Integer restartPeriod = charge.getRestartFrequency(); @@ -1238,12 +1232,12 @@ private void resetFreeChargeDaysCount(SavingsAccountCharge charge, final BigDeci YearMonth gapYearMonth = YearMonth.from(gapIntervalMonth); YearMonth localYearMonth = YearMonth.from(localDate); if (localYearMonth.isBefore(gapYearMonth)) { - countValidation(charge, transactionAmount, transactionDate, user, refNo); + countValidation(charge, transactionAmount, transactionDate, refNo); } else { discountCharge(1, charge); } } else { // calculation for days - Long completedDays; + long completedDays; if (resetDate == null) { completedDays = DAYS.between(DateUtils.getBusinessLocalDate(), this.activatedOnDate); @@ -1252,10 +1246,10 @@ private void resetFreeChargeDaysCount(SavingsAccountCharge charge, final BigDeci completedDays = DAYS.between(DateUtils.getBusinessLocalDate(), resetDate); } - int totalDays = completedDays.intValue(); + int totalDays = (int) completedDays; if (totalDays < restartPeriod) { - countValidation(charge, transactionAmount, transactionDate, user, refNo); + countValidation(charge, transactionAmount, transactionDate, refNo); } else { discountCharge(1, charge); } @@ -1263,7 +1257,7 @@ private void resetFreeChargeDaysCount(SavingsAccountCharge charge, final BigDeci } private void countValidation(SavingsAccountCharge charge, final BigDecimal transactionAmount, final LocalDate transactionDate, - final AppUser user, final String refNo) { + final String refNo) { boolean backdatedTxnsAllowedTill = false; if (charge.getFreeWithdrawalCount() < charge.getFrequencyFreeWithdrawalCharge()) { final Integer count = charge.getFreeWithdrawalCount() + 1; @@ -1271,7 +1265,7 @@ private void countValidation(SavingsAccountCharge charge, final BigDecimal trans charge.updateNoWithdrawalFee(); } else { charge.updateWithdralFeeAmount(transactionAmount); - this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, backdatedTxnsAllowedTill, refNo); + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, backdatedTxnsAllowedTill, refNo); } } @@ -1297,8 +1291,7 @@ public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate, final if (this.summary.getInterestPostedTillDate() == null) { return false; } - - transactionBeforeLastInterestPosting = this.summary.getInterestPostedTillDate().isAfter(transactionDate); + transactionBeforeLastInterestPosting = DateUtils.isBefore(transactionDate, this.summary.getInterestPostedTillDate()); } return transactionBeforeLastInterestPosting; @@ -1339,13 +1332,12 @@ public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transac if (depositAccountOnHoldTransactions != null) { for (final DepositAccountOnHoldTransaction onHoldTransaction : depositAccountOnHoldTransactions) { // Compare the balance of the on hold: - if ((onHoldTransaction.getTransactionDate().isBefore(transaction.transactionLocalDate()) - || onHoldTransaction.getTransactionDate().isEqual(transaction.transactionLocalDate())) - && (lastSavingsDate == null || onHoldTransaction.getTransactionDate().isAfter(lastSavingsDate))) { + if (!DateUtils.isAfter(onHoldTransaction.getTransactionDate(), transaction.getTransactionDate()) + && (lastSavingsDate == null || DateUtils.isAfter(onHoldTransaction.getTransactionDate(), lastSavingsDate))) { if (onHoldTransaction.getTransactionType().isHold()) { - minRequiredBalance = minRequiredBalance.plus(onHoldTransaction.getAmountMoney(this.currency)); + minRequiredBalance = minRequiredBalance.plus(onHoldTransaction.getAmount(this.currency)); } else { - minRequiredBalance = minRequiredBalance.minus(onHoldTransaction.getAmountMoney(this.currency)); + minRequiredBalance = minRequiredBalance.minus(onHoldTransaction.getAmount(this.currency)); } } } @@ -1359,7 +1351,7 @@ public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transac transactionAmount); } } - lastSavingsDate = transaction.transactionLocalDate(); + lastSavingsDate = transaction.getTransactionDate(); } @@ -1422,13 +1414,12 @@ public void validateAccountBalanceDoesNotBecomeNegative(final String transaction if (depositAccountOnHoldTransactions != null) { for (final DepositAccountOnHoldTransaction onHoldTransaction : depositAccountOnHoldTransactions) { // Compare the balance of the on hold: - if ((onHoldTransaction.getTransactionDate().isBefore(transaction.transactionLocalDate()) - || onHoldTransaction.getTransactionDate().isEqual(transaction.transactionLocalDate())) - && (lastSavingsDate == null || onHoldTransaction.getTransactionDate().isAfter(lastSavingsDate))) { + if (!DateUtils.isAfter(onHoldTransaction.getTransactionDate(), transaction.getTransactionDate()) + && (lastSavingsDate == null || DateUtils.isAfter(onHoldTransaction.getTransactionDate(), lastSavingsDate))) { if (onHoldTransaction.getTransactionType().isHold()) { - minRequiredBalance = minRequiredBalance.plus(onHoldTransaction.getAmountMoney(this.currency)); + minRequiredBalance = minRequiredBalance.plus(onHoldTransaction.getAmount(this.currency)); } else { - minRequiredBalance = minRequiredBalance.minus(onHoldTransaction.getAmountMoney(this.currency)); + minRequiredBalance = minRequiredBalance.minus(onHoldTransaction.getAmount(this.currency)); } } } @@ -1448,7 +1439,7 @@ public void validateAccountBalanceDoesNotBecomeNegative(final String transaction } } } - lastSavingsDate = transaction.transactionLocalDate(); + lastSavingsDate = transaction.getTransactionDate(); } BigDecimal withdrawalFee = null; @@ -1472,22 +1463,13 @@ public void validateAccountBalanceDoesNotViolateOverdraft(final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(resourceName + SavingsApiConstants.summitalAction); @@ -2122,17 +2075,17 @@ public void validateNewApplicationState(final LocalDate todayDateOfTenant, final throw new PlatformApiDataValidationException(dataValidationErrors); } - if (submittedOn.isAfter(todayDateOfTenant)) { + final LocalDate submittedOn = getSubmittedOnDate(); + if (DateUtils.isDateInTheFuture(submittedOn)) { baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(submittedOn) .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); } if (this.client != null && this.client.isActivatedAfter(submittedOn)) { - baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(this.client.getActivationLocalDate()) + baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(this.client.getActivationDate()) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); } else if (this.group != null && this.group.isActivatedAfter(submittedOn)) { - - baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(this.group.getActivationLocalDate()) + baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(this.group.getActivationDate()) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); } @@ -2141,14 +2094,6 @@ public void validateNewApplicationState(final LocalDate todayDateOfTenant, final } } - protected LocalDate getSubmittedOnLocalDate() { - return this.submittedOnDate; - } - - private LocalDate getApprovedOnLocalDate() { - return this.approvedOnDate; - } - public Client getClient() { return this.client; } @@ -2173,8 +2118,7 @@ public BigDecimal getNominalAnnualInterestRateOverdraft() { return this.nominalAnnualInterestRateOverdraft; } - public Map approveApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { - + public Map approveApplication(final AppUser currentUser, final JsonCommand command) { final Map actualChanges = new LinkedHashMap<>(); final List dataValidationErrors = new ArrayList<>(); @@ -2204,9 +2148,8 @@ public Map approveApplication(final AppUser currentUser, final J actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); actualChanges.put(SavingsApiConstants.approvedOnDateParamName, approvedOnDateChange); - final LocalDate submittalDate = getSubmittedOnLocalDate(); - if (approvedOn.isBefore(submittalDate)) { - + final LocalDate submittalDate = getSubmittedOnDate(); + if (DateUtils.isBefore(approvedOn, submittalDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); final String submittalDateAsString = formatter.format(submittalDate); @@ -2217,9 +2160,7 @@ public Map approveApplication(final AppUser currentUser, final J throw new PlatformApiDataValidationException(dataValidationErrors); } } - - if (approvedOn.isAfter(tenantsTodayDate)) { - + if (DateUtils.isAfterBusinessDate(approvedOn)) { baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName) .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); @@ -2295,7 +2236,7 @@ public void undoTransaction(final Long transactionId) { } validateAttemptToUndoTransferRelatedTransactions(transactionToUndo); - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.transactionLocalDate()); + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.getTransactionDate()); transactionToUndo.reverse(); if (transactionToUndo.isChargeTransaction() || transactionToUndo.isWaiveCharge()) { // undo charge @@ -2325,7 +2266,7 @@ public void undoSavingsTransaction(final Long transactionId) { } validateAttemptToUndoTransferRelatedTransactions(transactionToUndo); - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.transactionLocalDate()); + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.getTransactionDate()); transactionToUndo.reverse(); if (transactionToUndo.isChargeTransaction() || transactionToUndo.isWaiveCharge()) { // undo charge @@ -2333,9 +2274,9 @@ public void undoSavingsTransaction(final Long transactionId) { for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) { final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge(); if (transactionToUndo.isChargeTransaction()) { - chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); + chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.currency)); } else if (transactionToUndo.isWaiveCharge()) { - chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); + chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.currency)); } } } @@ -2348,7 +2289,7 @@ public void undoTransaction(final SavingsAccountTransaction transactionToUndo) { } validateAttemptToUndoTransferRelatedTransactions(transactionToUndo); - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.transactionLocalDate()); + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.getTransactionDate()); transactionToUndo.reverse(); if (transactionToUndo.isChargeTransaction() || transactionToUndo.isWaiveCharge()) { // undo charge @@ -2356,33 +2297,31 @@ public void undoTransaction(final SavingsAccountTransaction transactionToUndo) { for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) { final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge(); if (transactionToUndo.isChargeTransaction()) { - chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); + chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.currency)); } else if (transactionToUndo.isWaiveCharge()) { - chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); + chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.currency)); } } } } private LocalDate findLatestAnnualFeeTransactionDueDate() { - LocalDate nextDueDate = null; LocalDate lastAnnualFeeTransactionDate = null; for (final SavingsAccountTransaction transaction : retreiveOrderedNonInterestPostingTransactions()) { if (transaction.isAnnualFeeAndNotReversed()) { + LocalDate transactionDate = transaction.getTransactionDate(); if (lastAnnualFeeTransactionDate == null) { - lastAnnualFeeTransactionDate = transaction.transactionLocalDate(); + lastAnnualFeeTransactionDate = transactionDate; nextDueDate = lastAnnualFeeTransactionDate; } - - if (transaction.transactionLocalDate().isAfter(lastAnnualFeeTransactionDate)) { - lastAnnualFeeTransactionDate = transaction.transactionLocalDate(); + if (DateUtils.isAfter(transactionDate, lastAnnualFeeTransactionDate)) { + lastAnnualFeeTransactionDate = transactionDate; nextDueDate = lastAnnualFeeTransactionDate; } } } - return nextDueDate; } @@ -2409,8 +2348,7 @@ public void validateAccountBalanceDoesNotBecomeNegativeMinimal(final BigDecimal } } - public Map rejectApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { - + public Map rejectApplication(final AppUser currentUser, final JsonCommand command) { final Map actualChanges = new LinkedHashMap<>(); final List dataValidationErrors = new ArrayList<>(); @@ -2446,10 +2384,8 @@ public Map rejectApplication(final AppUser currentUser, final Js actualChanges.put(SavingsApiConstants.rejectedOnDateParamName, rejectedOnAsString); actualChanges.put(SavingsApiConstants.closedOnDateParamName, rejectedOnAsString); - final LocalDate submittalDate = getSubmittedOnLocalDate(); - - if (rejectedOn.isBefore(submittalDate)) { - + final LocalDate submittalDate = getSubmittedOnDate(); + if (DateUtils.isBefore(rejectedOn, submittalDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); final String submittalDateAsString = formatter.format(submittalDate); @@ -2460,9 +2396,7 @@ public Map rejectApplication(final AppUser currentUser, final Js throw new PlatformApiDataValidationException(dataValidationErrors); } } - - if (rejectedOn.isAfter(tenantsTodayDate)) { - + if (DateUtils.isAfterBusinessDate(rejectedOn)) { baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName).value(rejectedOn) .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); @@ -2475,8 +2409,7 @@ public Map rejectApplication(final AppUser currentUser, final Js return actualChanges; } - public Map applicantWithdrawsFromApplication(final AppUser currentUser, final JsonCommand command, - final LocalDate tenantsTodayDate) { + public Map applicantWithdrawsFromApplication(final AppUser currentUser, final JsonCommand command) { final Map actualChanges = new LinkedHashMap<>(); final List dataValidationErrors = new ArrayList<>(); @@ -2512,9 +2445,8 @@ public Map applicantWithdrawsFromApplication(final AppUser curre actualChanges.put(SavingsApiConstants.withdrawnOnDateParamName, withdrawnOnAsString); actualChanges.put(SavingsApiConstants.closedOnDateParamName, withdrawnOnAsString); - final LocalDate submittalDate = getSubmittedOnLocalDate(); - if (withdrawnOn.isBefore(submittalDate)) { - + final LocalDate submittalDate = getSubmittedOnDate(); + if (DateUtils.isBefore(withdrawnOn, submittalDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); final String submittalDateAsString = formatter.format(submittalDate); @@ -2525,9 +2457,7 @@ public Map applicantWithdrawsFromApplication(final AppUser curre throw new PlatformApiDataValidationException(dataValidationErrors); } } - - if (withdrawnOn.isAfter(tenantsTodayDate)) { - + if (DateUtils.isAfterBusinessDate(withdrawnOn)) { baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName).value(withdrawnOn) .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); @@ -2540,8 +2470,7 @@ public Map applicantWithdrawsFromApplication(final AppUser curre return actualChanges; } - public Map activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { - + public Map activate(final AppUser currentUser, final JsonCommand command) { final Map actualChanges = new LinkedHashMap<>(); final List dataValidationErrors = new ArrayList<>(); @@ -2577,14 +2506,14 @@ public Map activate(final AppUser currentUser, final JsonCommand this.closedBy = null; this.activatedOnDate = activationDate; this.activatedBy = currentUser; - this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); + this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationDate()); /* * if (annualFeeSettingsSet()) { updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); } */ if (this.client != null && this.client.isActivatedAfter(activationDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); - final String dateAsString = formatter.format(this.client.getActivationLocalDate()); + final String dateAsString = formatter.format(this.client.getActivationDate()); baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); if (!dataValidationErrors.isEmpty()) { @@ -2594,7 +2523,7 @@ public Map activate(final AppUser currentUser, final JsonCommand if (this.group != null && this.group.isActivatedAfter(activationDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); - final String dateAsString = formatter.format(this.client.getActivationLocalDate()); + final String dateAsString = formatter.format(this.client.getActivationDate()); baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.group.activation.date"); if (!dataValidationErrors.isEmpty()) { @@ -2602,9 +2531,8 @@ public Map activate(final AppUser currentUser, final JsonCommand } } - final LocalDate approvalDate = getApprovedOnLocalDate(); - if (activationDate.isBefore(approvalDate)) { - + final LocalDate approvalDate = getApprovedOnDate(); + if (DateUtils.isBefore(activationDate, approvalDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); final String dateAsString = formatter.format(approvalDate); @@ -2616,8 +2544,7 @@ public Map activate(final AppUser currentUser, final JsonCommand } } - if (activationDate.isAfter(tenantsTodayDate)) { - + if (DateUtils.isAfterBusinessDate(activationDate)) { baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(activationDate) .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); @@ -2660,11 +2587,11 @@ protected Map undoActivate(final AppUser currentUser, final Json this.closedBy = null; this.activatedOnDate = null; this.activatedBy = currentUser; - this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); + this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationDate()); if (this.client != null && this.client.isActivatedAfter(operationDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); - final String dateAsString = formatter.format(this.client.getActivationLocalDate()); + final String dateAsString = formatter.format(this.client.getActivationDate()); baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); if (!dataValidationErrors.isEmpty()) { @@ -2674,7 +2601,7 @@ protected Map undoActivate(final AppUser currentUser, final Json if (this.group != null && this.group.isActivatedAfter(operationDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); - final String dateAsString = formatter.format(this.client.getActivationLocalDate()); + final String dateAsString = formatter.format(this.client.getActivationDate()); baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.group.activation.date"); if (!dataValidationErrors.isEmpty()) { @@ -2682,7 +2609,7 @@ protected Map undoActivate(final AppUser currentUser, final Json } } - final LocalDate approvalDate = getApprovedOnLocalDate(); + final LocalDate approvalDate = getApprovedOnDate(); if (operationDate.isBefore(approvalDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); @@ -2723,15 +2650,14 @@ protected void reverseExistingTransactions() { } public void processAccountUponActivation(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth, final AppUser user) { - + final Integer financialYearBeginningMonth) { // update annual fee due date for (SavingsAccountCharge charge : this.charges()) { - charge.updateToNextDueDateFrom(getActivationLocalDate()); + charge.updateToNextDueDateFrom(getActivationDate()); } // auto pay the activation time charges (No need of checking the pivot date config) - this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user, false); + this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, false); // TODO : AA add activation charges to actual changes list } @@ -2751,18 +2677,18 @@ public void approveAndActivateApplication(final LocalDate appliedonDate, final A this.closedBy = null; this.activatedOnDate = appliedonDate; this.activatedBy = appliedBy; - this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); + this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationDate()); } private void payActivationCharges(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final AppUser user, final boolean backdatedTxnsAllowedTill) { + final boolean backdatedTxnsAllowedTill) { boolean isSavingsChargeApplied = false; boolean postReversals = false; final String refNo = ExternalId.generate().getValue(); for (SavingsAccountCharge savingsAccountCharge : this.charges()) { if (savingsAccountCharge.isSavingsActivation()) { isSavingsChargeApplied = true; - payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate(), user, + payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationDate(), backdatedTxnsAllowedTill, refNo.toString()); } } @@ -2771,10 +2697,11 @@ private void payActivationCharges(final boolean isSavingsInterestPostingAtCurren final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; LocalDate postInterestAsOnDate = null; - if (this.isBeforeLastPostingPeriod(getActivationLocalDate(), backdatedTxnsAllowedTill)) { + if (this.isBeforeLastPostingPeriod(getActivationDate(), backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - // this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - // postInterestAsOnDate, backdatedTxnsAllowedTill, postReversals); + // this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + // financialYearBeginningMonth, + // postInterestAsOnDate, backdatedTxnsAllowedTill, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); this.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -2783,8 +2710,7 @@ private void payActivationCharges(final boolean isSavingsInterestPostingAtCurren } } - public Map close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { - + public Map close(final AppUser currentUser, final JsonCommand command) { final Map actualChanges = new LinkedHashMap<>(); final List dataValidationErrors = new ArrayList<>(); @@ -2803,14 +2729,14 @@ public Map close(final AppUser currentUser, final JsonCommand co final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - if (closedDate.isBefore(getActivationLocalDate())) { + if (DateUtils.isBefore(closedDate, getActivationDate())) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("must.be.after.activation.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (closedDate.isAfter(tenantsTodayDate)) { + if (DateUtils.isAfterBusinessDate(closedDate)) { baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) .failWithCode("cannot.be.a.future.date"); if (!dataValidationErrors.isEmpty()) { @@ -2852,9 +2778,9 @@ public Map close(final AppUser currentUser, final JsonCommand co } protected void validateActivityNotBeforeClientOrGroupTransferDate(final SavingsEvent event, final LocalDate activityDate) { - if (this.client != null && this.client.getOfficeJoiningLocalDate() != null) { - final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate(); - if (activityDate.isBefore(clientOfficeJoiningDate)) { + if (this.client != null) { + final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate(); + if (DateUtils.isBefore(activityDate, clientOfficeJoiningDate)) { throw new SavingsActivityPriorToClientTransferException(event.toString(), clientOfficeJoiningDate); } } @@ -2984,8 +2910,7 @@ public void removeCharge(final SavingsAccountCharge charge) { this.charges.remove(charge); } - public void waiveCharge(final Long savingsAccountChargeId, final AppUser user, final boolean backdatedTxnsAllowedTill) { - + public void waiveCharge(final Long savingsAccountChargeId, final boolean backdatedTxnsAllowedTill) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); @@ -3035,8 +2960,7 @@ public void waiveCharge(final Long savingsAccountChargeId, final AppUser user, f // waive charge final Money amountWaived = savingsAccountCharge.waive(getCurrency()); - handleWaiverChargeTransactions(savingsAccountCharge, amountWaived, user, backdatedTxnsAllowedTill); - + handleWaiverChargeTransactions(savingsAccountCharge, amountWaived, backdatedTxnsAllowedTill); } public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, @@ -3061,15 +2985,15 @@ public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCha } } - final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate(); + final LocalDate chargeDueDate = savingsAccountCharge.getDueDate(); if (savingsAccountCharge.isOnSpecifiedDueDate()) { - if (getActivationLocalDate() != null && chargeDueDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().format(formatter)) + if (DateUtils.isBefore(chargeDueDate, getActivationDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationDate().format(formatter)) .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); throw new PlatformApiDataValidationException(dataValidationErrors); - } else if (getSubmittedOnLocalDate() != null && chargeDueDate.isBefore(getSubmittedOnLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().format(formatter)) + } else if (DateUtils.isBefore(chargeDueDate, getSubmittedOnDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnDate().format(formatter)) .failWithCodeNoParameterAddedToErrorCode("before.submittedOnDate"); throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -3092,9 +3016,9 @@ public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCha if (savingsAccountCharge.isAnnualFee() || savingsAccountCharge.isMonthlyFee() || savingsAccountCharge.isWeeklyFee()) { // update due date if (isActive()) { - savingsAccountCharge.updateToNextDueDateFrom(getActivationLocalDate()); + savingsAccountCharge.updateToNextDueDateFrom(getActivationDate()); } else if (isApproved()) { - savingsAccountCharge.updateToNextDueDateFrom(getApprovedOnLocalDate()); + savingsAccountCharge.updateToNextDueDateFrom(getApprovedOnDate()); } } @@ -3118,9 +3042,8 @@ private boolean isAnnualFeeExists() { } public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, - final LocalDate transactionDate, final DateTimeFormatter formatter, final AppUser user, final boolean backdatedTxnsAllowedTill, + final LocalDate transactionDate, final DateTimeFormatter formatter, final boolean backdatedTxnsAllowedTill, final String refNo) { - final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); @@ -3146,8 +3069,8 @@ public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAcc } } - if (getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().format(formatter)) + if (DateUtils.isBefore(transactionDate, getActivationDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationDate().format(formatter)) .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -3165,19 +3088,19 @@ public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAcc } if (savingsAccountCharge.isAnnualFee()) { - final LocalDate annualFeeDueDate = savingsAccountCharge.getDueLocalDate(); + final LocalDate annualFeeDueDate = savingsAccountCharge.getDueDate(); if (annualFeeDueDate == null) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.annualfee.settings"); throw new PlatformApiDataValidationException(dataValidationErrors); } - if (!annualFeeDueDate.equals(transactionDate)) { + if (!DateUtils.isEqual(annualFeeDueDate, transactionDate)) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("invalid.date"); throw new PlatformApiDataValidationException(dataValidationErrors); } LocalDate currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate(); - if (currentAnnualFeeNextDueDate != null && currentAnnualFeeNextDueDate.isEqual(transactionDate)) { + if (DateUtils.isEqual(currentAnnualFeeNextDueDate, transactionDate)) { baseDataValidator.reset().parameter("dueDate").value(transactionDate.format(formatter)) .failWithCodeNoParameterAddedToErrorCode("transaction.exists.on.date"); @@ -3206,35 +3129,35 @@ public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAcc } } - return this.payCharge(savingsAccountCharge, chargePaid, transactionDate, user, backdatedTxnsAllowedTill, refNo); + return this.payCharge(savingsAccountCharge, chargePaid, transactionDate, backdatedTxnsAllowedTill, refNo); } public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, - final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill, String refNo) { + final LocalDate transactionDate, final boolean backdatedTxnsAllowedTill, String refNo) { savingsAccountCharge.pay(getCurrency(), amountPaid); - return handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user, backdatedTxnsAllowedTill, refNo); + return handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, backdatedTxnsAllowedTill, refNo); } private SavingsAccountTransaction handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, - final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill, final String refNo) { - SavingsAccountTransaction chargeTransaction = null; + final LocalDate transactionDate, final boolean backdatedTxnsAllowedTill, final String refNo) { + SavingsAccountTransaction chargeTransaction; if (savingsAccountCharge.isWithdrawalFee()) { - chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount, user, refNo); + chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount, refNo); } else if (savingsAccountCharge.isAnnualFee()) { - chargeTransaction = SavingsAccountTransaction.annualFee(this, office(), transactionDate, transactionAmount, user); + chargeTransaction = SavingsAccountTransaction.annualFee(this, office(), transactionDate, transactionAmount); } else { - chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount, user); + chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount); } handleChargeTransactions(savingsAccountCharge, chargeTransaction, backdatedTxnsAllowedTill); return chargeTransaction; } - private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, AppUser user, + private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, boolean backdatedTxnsAllowedTill) { final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.waiver(this, office(), - DateUtils.getBusinessLocalDate(), transactionAmount, user); + DateUtils.getBusinessLocalDate(), transactionAmount); handleChargeTransactions(savingsAccountCharge, chargeTransaction, backdatedTxnsAllowedTill); } @@ -3243,7 +3166,7 @@ private void handleChargeTransactions(final SavingsAccountCharge savingsAccountC // Provide a link between transaction and savings charge for which // amount is waived. final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(transaction, savingsAccountCharge, - transaction.getAmount(this.getCurrency()).getAmount()); + transaction.getAmount(this.currency).getAmount()); transaction.getSavingsAccountChargesPaid().add(chargePaidBy); if (backdatedTxnsAllowedTill) { this.savingsAccountTransactions.add(transaction); @@ -3255,7 +3178,7 @@ private void handleChargeTransactions(final SavingsAccountCharge savingsAccountC // Charge Accrual Recognition final SavingsAccountTransaction savingsAccountAccrualTransaction = handleAccruedChargeAppliedTransaction( - transaction.transactionLocalDate(), savingsAccountCharge); + transaction.getTransactionDate(), savingsAccountCharge); if (savingsAccountAccrualTransaction != null) { savingsAccountAccrualTransaction.getSavingsAccountChargesPaid().add(chargePaidBy); if (backdatedTxnsAllowedTill) { @@ -3266,12 +3189,13 @@ private void handleChargeTransactions(final SavingsAccountCharge savingsAccountC } } - private SavingsAccountTransaction handleAccruedChargeAppliedTransaction(final LocalDate transactionDate, final SavingsAccountCharge savingsAccountCharge) { + private SavingsAccountTransaction handleAccruedChargeAppliedTransaction(final LocalDate transactionDate, + final SavingsAccountCharge savingsAccountCharge) { SavingsAccountTransaction savingsAccountAccrualTransaction = null; if (isPeriodicAccrualAccounting()) { if (isChargeToBeRecognizedAsAccrual(savingsAccountCharge)) { - savingsAccountAccrualTransaction = SavingsAccountTransaction.accrual(this, - office(), transactionDate, savingsAccountCharge.getAmount(getCurrency()) , false); + savingsAccountAccrualTransaction = SavingsAccountTransaction.accrual(this, office(), transactionDate, + savingsAccountCharge.getAmount(getCurrency()), false); } } return savingsAccountAccrualTransaction; @@ -3329,7 +3253,7 @@ public boolean allowOverdraft() { } public LocalDate accountSubmittedOrActivationDate() { - return getActivationLocalDate() == null ? getSubmittedOnLocalDate() : getActivationLocalDate(); + return getActivationDate() == null ? getSubmittedOnDate() : getActivationDate(); } public DepositAccountType depositAccountType() { @@ -3341,35 +3265,29 @@ protected boolean isTransferInterestToOtherAccount() { } public boolean accountSubmittedAndActivationOnSameDate() { - if (getSubmittedOnLocalDate() == null || getActivationLocalDate() == null) { - return false; - } - return getActivationLocalDate().isEqual(getSubmittedOnLocalDate()); + return getActivationDate() != null && DateUtils.isEqual(getActivationDate(), getSubmittedOnDate()); } public void validateInterestPostingAndCompoundingPeriodTypes(final DataValidatorBuilder baseDataValidator) { Map> postingtoCompoundMap = new HashMap<>(); - postingtoCompoundMap.put(SavingsPostingInterestPeriodType.DAILY, - Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY })); + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.DAILY, Arrays.asList(SavingsCompoundingInterestPeriodType.DAILY)); - postingtoCompoundMap.put(SavingsPostingInterestPeriodType.MONTHLY, Arrays.asList(new SavingsCompoundingInterestPeriodType[] { - SavingsCompoundingInterestPeriodType.DAILY, SavingsCompoundingInterestPeriodType.MONTHLY })); + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.MONTHLY, + Arrays.asList(SavingsCompoundingInterestPeriodType.DAILY, SavingsCompoundingInterestPeriodType.MONTHLY)); - postingtoCompoundMap.put(SavingsPostingInterestPeriodType.QUATERLY, - Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY, - SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY })); + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.QUATERLY, Arrays.asList(SavingsCompoundingInterestPeriodType.DAILY, + SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY)); postingtoCompoundMap.put(SavingsPostingInterestPeriodType.BIANNUAL, - Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY, - SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY, - SavingsCompoundingInterestPeriodType.BI_ANNUAL })); + Arrays.asList(SavingsCompoundingInterestPeriodType.DAILY, SavingsCompoundingInterestPeriodType.MONTHLY, + SavingsCompoundingInterestPeriodType.QUATERLY, SavingsCompoundingInterestPeriodType.BI_ANNUAL)); postingtoCompoundMap.put(SavingsPostingInterestPeriodType.ANNUAL, - Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY, - SavingsCompoundingInterestPeriodType.MONTHLY, SavingsCompoundingInterestPeriodType.QUATERLY, - SavingsCompoundingInterestPeriodType.BI_ANNUAL, SavingsCompoundingInterestPeriodType.ANNUAL })); + Arrays.asList(SavingsCompoundingInterestPeriodType.DAILY, SavingsCompoundingInterestPeriodType.MONTHLY, + SavingsCompoundingInterestPeriodType.QUATERLY, SavingsCompoundingInterestPeriodType.BI_ANNUAL, + SavingsCompoundingInterestPeriodType.ANNUAL)); SavingsPostingInterestPeriodType savingsPostingInterestPeriodType = SavingsPostingInterestPeriodType .fromInt(interestPostingPeriodType); @@ -3424,7 +3342,7 @@ public boolean isTransactionAllowed(SavingsAccountTransactionType transactionTyp if (transactionDate == null) { return true; } - if (DateUtils.isDateInTheFuture(transactionDate) || transactionDate.isBefore(getActivationLocalDate())) { + if (DateUtils.isDateInTheFuture(transactionDate) || DateUtils.isBefore(transactionDate, getActivationDate())) { return false; } if (transactionType.isCredit()) { @@ -3538,14 +3456,14 @@ protected boolean applyWithholdTaxForDepositAccounts(final LocalDate interestPos return recalucateDailyBalance; } - public void setSubStatusInactive(AppUser appUser, final boolean backdatedTxnsAllowedTill) { + public void setSubStatusInactive(final boolean backdatedTxnsAllowedTill) { this.sub_status = SavingsAccountSubStatusEnum.INACTIVE.getValue(); LocalDate transactionDate = DateUtils.getBusinessLocalDate(); for (SavingsAccountCharge charge : this.charges()) { if (charge.isSavingsNoActivity() && charge.isActive()) { charge.updateWithdralFeeAmount(this.getAccountBalance()); final String refNo = ExternalId.generate().getValue(); - this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, appUser, backdatedTxnsAllowedTill, + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, backdatedTxnsAllowedTill, refNo.toString()); } } @@ -3567,17 +3485,13 @@ public void escheat(AppUser appUser) { boolean postReversals = false; LocalDate transactionDate = DateUtils.getBusinessLocalDate(); if (this.getSummary().getAccountBalance(this.getCurrency()).isGreaterThanZero()) { - SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, appUser, postInterestAsOnDate); + SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, postInterestAsOnDate); this.transactions.add(transaction); } recalculateDailyBalances(Money.zero(this.currency), transactionDate, false, postReversals); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); } - public void loadTransactions() { - transactions.size(); - } - public void loadLazyCollections() { transactions.size(); charges.size(); @@ -3789,21 +3703,21 @@ public Integer getSubStatus() { public void validateForAccountBlock() { final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.getSubStatus()); if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus)) { - throw new SavingsAccountBlockedException(this.getId()); + throw new SavingsAccountBlockedException(this.getAccountNumber()); } } public void validateForDebitBlock() { final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.getSubStatus()); if (SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(currentSubStatus)) { - throw new SavingsAccountDebitsBlockedException(this.getId()); + throw new SavingsAccountDebitsBlockedException(this.getAccountNumber()); } } public void validateForCreditBlock() { final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.getSubStatus()); if (SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(currentSubStatus)) { - throw new SavingsAccountCreditsBlockedException(this.getId()); + throw new SavingsAccountCreditsBlockedException(this.getAccountNumber()); } } @@ -3815,7 +3729,7 @@ public LocalDate retrieveLastTransactionDate() { } LocalDate lastransactionDate = null; if (lastTransaction != null) { - lastransactionDate = lastTransaction.transactionLocalDate(); + lastransactionDate = lastTransaction.getTransactionDate(); } return lastransactionDate; } @@ -3828,7 +3742,7 @@ public LocalDate retrieveLastTransactionDateWithPivotConfig() { } LocalDate lastransactionDate = null; if (lastTransaction != null) { - lastransactionDate = lastTransaction.transactionLocalDate(); + lastransactionDate = lastTransaction.getTransactionDate(); } return lastransactionDate; } @@ -3897,10 +3811,6 @@ public BigDecimal getMinOverdraftForInterestCalculation() { return this.minOverdraftForInterestCalculation; } - public LocalDate getLockedInUntilDate() { - return this.lockedInUntilDate; - } - public Integer getDepositType() { return this.depositType; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java index b541a2d1d3b..0fd16e6dbba 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java @@ -56,7 +56,6 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.organisation.staff.domain.Staff; import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper; @@ -333,7 +332,7 @@ public SavingsAccount assembleFrom(final JsonCommand command, final AppUser subm nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); - account.validateNewApplicationState(DateUtils.getBusinessLocalDate(), SAVINGS_ACCOUNT_RESOURCE_NAME); + account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); account.validateAccountValuesWithProduct(); @@ -349,7 +348,7 @@ public SavingsAccount loadTransactionsToSavingsAccount(final SavingsAccount acco List savingsAccountTransactions = null; if (backdatedTxnsAllowedTill) { LocalDate pivotDate = account.getSummary().getInterestPostedTillDate(); - boolean isNotPresent = pivotDate == null ? true : false; + boolean isNotPresent = pivotDate == null; if (!isNotPresent) { // Get savings account transactions if (isRelaxingDaysConfigForPivotDateEnabled()) { @@ -468,7 +467,7 @@ public SavingsAccount assembleFrom(final Client client, final Group group, final product.withHoldTax()); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); - account.validateNewApplicationState(DateUtils.getBusinessLocalDate(), SAVINGS_ACCOUNT_RESOURCE_NAME); + account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); account.validateAccountValuesWithProduct(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java index d4e47e6eb54..d0f1cd6ce24 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java @@ -32,8 +32,6 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; -import lombok.extern.slf4j.Slf4j; - import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; @@ -42,8 +40,11 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; @@ -59,7 +60,7 @@ @Slf4j @Entity @Table(name = "m_savings_account_charge") -public class SavingsAccountCharge extends AbstractPersistableCustom { +public class SavingsAccountCharge extends AbstractAuditableWithUTCDateTimeCustom { @ManyToOne(optional = false) @JoinColumn(name = "savings_account_id", referencedColumnName = "id", nullable = false) @@ -447,7 +448,7 @@ public Map update(final JsonCommand command) { final String dateFormatAsInput = command.dateFormat(); final String localeAsInput = command.locale(); - if (command.isChangeInLocalDateParameterNamed(dueAsOfDateParamName, getDueLocalDate())) { + if (command.isChangeInLocalDateParameterNamed(dueAsOfDateParamName, getDueDate())) { final String valueAsInput = command.stringValueOfParameterNamed(dueAsOfDateParamName); actualChanges.put(dueAsOfDateParamName, valueAsInput); actualChanges.put(dateFormatParamName, dateFormatAsInput); @@ -520,7 +521,7 @@ private boolean isGreaterThanZero(final BigDecimal value) { return value.compareTo(BigDecimal.ZERO) > 0; } - public LocalDate getDueLocalDate() { + public LocalDate getDueDate() { return this.dueDate; } @@ -753,17 +754,13 @@ public boolean equals(Object o) { SavingsAccountCharge that = (SavingsAccountCharge) o; return (penaltyCharge == that.penaltyCharge) && (paid == that.paid) && (waived == that.waived) && (status == that.status) && Objects.equals(savingsAccount, that.savingsAccount) && Objects.equals(charge, that.charge) - && Objects.equals(chargeTime, that.chargeTime) && dueDate.compareTo(that.dueDate) == 0 - ? Boolean.TRUE - : Boolean.FALSE && Objects.equals(feeOnMonth, that.feeOnMonth) && Objects.equals(feeOnDay, that.feeOnDay) - && Objects.equals(feeInterval, that.feeInterval) - && Objects.equals(chargeCalculation, that.chargeCalculation) && Objects.equals(percentage, that.percentage) - && Objects.equals(amountPercentageAppliedTo, that.amountPercentageAppliedTo) - && Objects.equals(amount, that.amount) && Objects.equals(amountPaid, that.amountPaid) - && Objects.equals(amountWaived, that.amountWaived) - && Objects.equals(amountWrittenOff, that.amountWrittenOff) - && Objects.equals(amountOutstanding, that.amountOutstanding) - && inactivationDate.compareTo(that.inactivationDate) == 0 ? Boolean.TRUE : Boolean.FALSE; + && Objects.equals(chargeTime, that.chargeTime) && DateUtils.isEqual(dueDate, that.dueDate) + && Objects.equals(feeOnMonth, that.feeOnMonth) && Objects.equals(feeOnDay, that.feeOnDay) + && Objects.equals(feeInterval, that.feeInterval) && Objects.equals(chargeCalculation, that.chargeCalculation) + && Objects.equals(percentage, that.percentage) && Objects.equals(amountPercentageAppliedTo, that.amountPercentageAppliedTo) + && Objects.equals(amount, that.amount) && Objects.equals(amountPaid, that.amountPaid) + && Objects.equals(amountWaived, that.amountWaived) && Objects.equals(amountWrittenOff, that.amountWrittenOff) + && Objects.equals(amountOutstanding, that.amountOutstanding) && DateUtils.isEqual(inactivationDate, that.inactivationDate); } @Override @@ -802,12 +799,12 @@ public LocalDate getNextDueDateFrom(final LocalDate startingDate) { if (isAnnualFee() || isMonthlyFee()) { nextDueLocalDate = startingDate.withMonth(this.feeOnMonth); nextDueLocalDate = setDayOfMonth(nextDueLocalDate); - while (startingDate.isAfter(nextDueLocalDate)) { + while (DateUtils.isBefore(nextDueLocalDate, startingDate)) { nextDueLocalDate = calculateNextDueDate(nextDueLocalDate); } } else if (isWeeklyFee()) { - nextDueLocalDate = getDueLocalDate(); - while (startingDate.isAfter(nextDueLocalDate)) { + nextDueLocalDate = getDueDate(); + while (DateUtils.isBefore(nextDueLocalDate, startingDate)) { nextDueLocalDate = calculateNextDueDate(nextDueLocalDate); } } else { @@ -882,12 +879,11 @@ public boolean isRecurringFee() { } public boolean isChargeIsDue(final LocalDate nextDueDate) { - return this.getDueLocalDate().isBefore(nextDueDate); + return DateUtils.isBefore(getDueDate(), nextDueDate); } public boolean isChargeIsOverPaid(final LocalDate nextDueDate) { - final BigDecimal amountPaid = this.amountPaid == null ? BigDecimal.ZERO : amountPaid(); - return this.getDueLocalDate().isAfter(nextDueDate) && amountPaid.compareTo(BigDecimal.ZERO) > 0; + return DateUtils.isAfter(getDueDate(), nextDueDate) && MathUtil.isGreaterThanZero(amountPaid()); } private BigDecimal amountPaid() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java index 14545b67225..c2adaa67441 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java @@ -26,7 +26,6 @@ import java.util.Set; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; -import org.apache.fineract.useradministration.domain.AppUser; public interface SavingsAccountDomainService { @@ -47,11 +46,9 @@ SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate SavingsAccountTransaction handleReversal(SavingsAccount account, List savingsAccountTransactions, boolean backdatedTxnsAllowedTill); - SavingsAccountTransaction handleHold(SavingsAccount account, AppUser createdUser, BigDecimal amount, LocalDate transactionDate, - Boolean lienAllowed); - void postInterest(SavingsAccount account, MathContext mc, LocalDate interestPostingUpToDate, boolean isInterestTransfer, - boolean isSavingsInterestPostingAtCurrentPeriodEnd, Integer financialYearBeginningMonth, - LocalDate postInterestOnDate, boolean backdatedTxnsAllowedTill, boolean postReversals); - + boolean isSavingsInterestPostingAtCurrentPeriodEnd, Integer financialYearBeginningMonth, LocalDate postInterestOnDate, + boolean backdatedTxnsAllowedTill, boolean postReversals); + + SavingsAccountTransaction handleHold(SavingsAccount account, BigDecimal amount, LocalDate transactionDate, Boolean lienAllowed); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index b31e94b68be..e9d3e8f3b95 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.domain.ExternalId; @@ -44,12 +45,9 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO; import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod; import org.apache.fineract.portfolio.savings.exception.DepositAccountTransactionNotAllowedException; -import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; - @Service @RequiredArgsConstructor public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService { @@ -67,8 +65,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final SavingsTransactionBooleanValues transactionBooleanValues, final boolean backdatedTxnsAllowedTill) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); account.validateForAccountBlock(); account.validateForDebitBlock(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService @@ -91,7 +88,7 @@ public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, Integer accountType = null; final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, DateUtils.getLocalDateTimeOfSystem(), user, accountType); + paymentDetail, null, accountType); final String refNo = ExternalId.generate().getValue(); final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee(), backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo); @@ -131,14 +128,6 @@ public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, return withdrawal; } - private AppUser getAppUserIfPresent() { - AppUser user = null; - if (this.context != null) { - user = this.context.getAuthenticatedUserIfPresent(); - } - return user; - } - @Transactional @Override public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, @@ -153,7 +142,7 @@ private SavingsAccountTransaction handleDeposit(final SavingsAccount account, fi final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final boolean isAccountTransfer, final boolean isRegularTransaction, final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) { - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); account.validateForAccountBlock(); account.validateForCreditBlock(); @@ -177,7 +166,7 @@ private SavingsAccountTransaction handleDeposit(final SavingsAccount account, fi Integer accountType = null; final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, DateUtils.getLocalDateTimeOfSystem(), user, accountType); + paymentDetail, null, accountType); final String refNo = ExternalId.generate().getValue(); final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType, backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo); @@ -210,13 +199,10 @@ private SavingsAccountTransaction handleDeposit(final SavingsAccount account, fi @Transactional @Override - public SavingsAccountTransaction handleHold(final SavingsAccount account, final AppUser createdUser, BigDecimal amount, - LocalDate transactionDate, Boolean lienAllowed) { - final PaymentDetail paymentDetails = null; - - SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(account, account.office(), paymentDetails, - transactionDate, Money.of(account.getCurrency(), amount), DateUtils.getLocalDateTimeOfSystem(), createdUser, lienAllowed); - return transaction; + public SavingsAccountTransaction handleHold(final SavingsAccount account, BigDecimal amount, LocalDate transactionDate, + Boolean lienAllowed) { + return SavingsAccountTransaction.holdAmount(account, account.office(), null, transactionDate, + Money.of(account.getCurrency(), amount), lienAllowed); } @Override @@ -306,15 +292,15 @@ public SavingsAccountTransaction handleReversal(SavingsAccount account, List postingPeriods = account.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); @@ -370,11 +357,12 @@ public void postInterest(SavingsAccount account, final MathContext mc, final Loc SavingsAccountTransaction newPostingTransaction; if (interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(currency))) { - newPostingTransaction = SavingsAccountTransaction.interestPosting(account, account.office(), interestPostingTransactionDate, - interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); + newPostingTransaction = SavingsAccountTransaction.interestPosting(account, account.office(), + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); } else { - newPostingTransaction = SavingsAccountTransaction.overdraftInterest(account, account.office(), interestPostingTransactionDate, - interestEarnedToBePostedForPeriod.negated(), interestPostingPeriod.isUserPosting()); + newPostingTransaction = SavingsAccountTransaction.overdraftInterest(account, account.office(), + interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(), + interestPostingPeriod.isUserPosting()); } if (backdatedTxnsAllowedTill) { account.addTransactionToExisting(newPostingTransaction); @@ -383,8 +371,7 @@ public void postInterest(SavingsAccount account, final MathContext mc, final Loc } if (account.savingsProduct().isAccrualBasedAccountingEnabled()) { SavingsAccountTransaction accrualTransaction = SavingsAccountTransaction.accrual(account, account.office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod, - interestPostingPeriod.isUserPosting()); + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); if (backdatedTxnsAllowedTill) { account.addTransactionToExisting(accrualTransaction); } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java index c8586c6cdd7..03880fb606e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java @@ -216,7 +216,7 @@ private HashMap updateRunningBalanceAndPivotDate(final boolean ba final SavingsAccountTransaction savingsAccountTransaction = savingsAccountTransactions.get(i); if (savingsAccountTransaction.isInterestPostingAndNotReversed() && !savingsAccountTransaction.isReversalTransaction() && !isUpdated) { - setInterestPostedTillDate(savingsAccountTransaction.getLastTransactionDate()); + setInterestPostedTillDate(savingsAccountTransaction.getTransactionDate()); isUpdated = true; if (!backdatedTxnsAllowedTill) { break; @@ -224,7 +224,7 @@ private HashMap updateRunningBalanceAndPivotDate(final boolean ba } if (savingsAccountTransaction.isOverdraftInterestAndNotReversed() && !savingsAccountTransaction.isReversalTransaction() && !isUpdated) { - setInterestPostedTillDate(savingsAccountTransaction.getLastTransactionDate()); + setInterestPostedTillDate(savingsAccountTransaction.getTransactionDate()); isUpdated = true; if (!backdatedTxnsAllowedTill) { break; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java index d4846ff74a5..0d31667fc94 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.savings.domain; +import static org.apache.fineract.infrastructure.core.service.DateUtils.getSystemZoneId; + import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -29,14 +31,16 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -51,7 +55,6 @@ import org.apache.fineract.portfolio.savings.domain.interest.SavingsAccountTransactionDetailsForPostingPeriod; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; import org.apache.fineract.portfolio.tax.domain.TaxComponent; -import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.util.CollectionUtils; /** @@ -59,7 +62,7 @@ */ @Entity @Table(name = "m_savings_account_transaction") -public final class SavingsAccountTransaction extends AbstractPersistableCustom { +public final class SavingsAccountTransaction extends AbstractAuditableWithUTCDateTimeCustom { @ManyToOne(optional = false) @JoinColumn(name = "savings_account_id", referencedColumnName = "id", nullable = false) @@ -103,16 +106,13 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { @Column(name = "overdraft_amount_derived", scale = 6, precision = 19, nullable = true) private BigDecimal overdraftAmount; - @Column(name = "created_date", nullable = false) - private LocalDateTime createdDate; + @Deprecated + @Column(name = "created_date", nullable = true) + private LocalDateTime createdDateToRemove; @Column(name = "submitted_on_date", nullable = false) private LocalDate submittedOnDate; - @ManyToOne - @JoinColumn(name = "appuser_id", nullable = true) - private AppUser appUser; - @Column(name = "is_manual", length = 1, nullable = true) private boolean isManualTransaction; @@ -144,16 +144,11 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { @Column(name = "ref_no", nullable = true) private String refNo; - SavingsAccountTransaction() { - this.dateOf = null; - this.typeOf = null; - this.createdDate = null; - } + SavingsAccountTransaction() {} private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, - final Integer typeOf, final LocalDate transactionLocalDate, final LocalDateTime createdDate, final BigDecimal amount, - final boolean isReversed, final AppUser appUser, final boolean isManualTransaction, final Boolean lienTransaction, - final String refNo) { + final Integer typeOf, final LocalDate transactionLocalDate, final BigDecimal amount, final boolean isReversed, + final boolean isManualTransaction, final Boolean lienTransaction, final String refNo) { this.savingsAccount = savingsAccount; this.office = office; this.typeOf = typeOf; @@ -161,42 +156,52 @@ private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Off this.amount = amount; this.reversed = isReversed; this.paymentDetail = paymentDetail; - this.createdDate = createdDate; + this.createdDateToRemove = null; // #audit backward compatibility deprecated this.submittedOnDate = DateUtils.getBusinessLocalDate(); - this.appUser = appUser; this.isManualTransaction = isManualTransaction; this.lienTransaction = lienTransaction; this.refNo = refNo; } + private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final Integer typeOf, + final LocalDate transactionLocalDate, final Money amount, final boolean isReversed, final boolean isManualTransaction, + final Boolean lienTransaction, final String refNo) { + this(savingsAccount, office, null, typeOf, transactionLocalDate, amount, isReversed, isManualTransaction, lienTransaction, refNo); + } + + private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, + final Integer typeOf, final LocalDate transactionLocalDate, final Money amount, final boolean isReversed, + final boolean isManualTransaction, final Boolean lienTransaction, final String refNo) { + this(savingsAccount, office, paymentDetail, typeOf, transactionLocalDate, amount.getAmount(), isReversed, isManualTransaction, + lienTransaction, refNo); + } + public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, LocalDateTime createdDate, final AppUser appUser, - final String refNo) { + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, final String refNo) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.DEPOSIT.getValue(), date, - createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); + amount, isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, LocalDateTime createdDate, final AppUser appUser, + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, final SavingsAccountTransactionType savingsAccountTransactionType, final String refNo) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; - return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, savingsAccountTransactionType.getValue(), date, - createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); + return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, savingsAccountTransactionType.getValue(), date, amount, + isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction withdrawal(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, LocalDateTime createdDate, final AppUser appUser, - final String refNo) { + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, final String refNo) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.WITHDRAWAL.getValue(), - date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); + date, amount, isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction accrual(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -204,8 +209,8 @@ public static SavingsAccountTransaction accrual(final SavingsAccount savingsAcco final boolean isReversed = false; final Boolean lienTransaction = false; final String refNo = ExternalId.generate().getValue(); - return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ACCRUAL.getValue(), date, - amount, isReversed, null, isManualTransaction, lienTransaction, refNo); + return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ACCRUAL.getValue(), date, amount, + isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction interestPosting(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -214,7 +219,7 @@ public static SavingsAccountTransaction interestPosting(final SavingsAccount sav final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.INTEREST_POSTING.getValue(), date, - amount, isReversed, null, isManualTransaction, lienTransaction, refNo); + amount, isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction overdraftInterest(final SavingsAccount savingsAccount, final Office office, @@ -223,82 +228,82 @@ public static SavingsAccountTransaction overdraftInterest(final SavingsAccount s final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue(), date, - amount, isReversed, null, isManualTransaction, lienTransaction, refNo); + amount, isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount, final AppUser appUser, final String refNo) { + final Money amount, final String refNo) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue(), date, amount, - isReversed, appUser, isManualTransaction, lienTransaction, refNo); + isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction annualFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount, final AppUser appUser) { + final Money amount) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount, - isReversed, appUser, isManualTransaction, lienTransaction, refNo); + isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction charge(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount, final AppUser appUser) { + final Money amount) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.PAY_CHARGE.getValue(), date, amount, - isReversed, appUser, isManualTransaction, lienTransaction, refNo); + isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction waiver(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount, final AppUser appUser) { + final Money amount) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WAIVE_CHARGES.getValue(), date, amount, - isReversed, appUser, isManualTransaction, lienTransaction, refNo); + isReversed, isManualTransaction, lienTransaction, refNo); } - public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final AppUser appUser) { + public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, + final LocalDate date) { final boolean isReversed = false; final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, - SavingsAccountTransactionType.INITIATE_TRANSFER.getValue(), date, DateUtils.getLocalDateTimeOfSystem(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction, refNo); + SavingsAccountTransactionType.INITIATE_TRANSFER.getValue(), date, savingsAccount.getSummary().getAccountBalance(), + isReversed, isManualTransaction, lienTransaction, refNo); } - public static SavingsAccountTransaction approveTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final AppUser appUser) { + public static SavingsAccountTransaction approveTransfer(final SavingsAccount savingsAccount, final Office office, + final LocalDate date) { final boolean isReversed = false; final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, - SavingsAccountTransactionType.APPROVE_TRANSFER.getValue(), date, DateUtils.getLocalDateTimeOfSystem(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction, refNo); + SavingsAccountTransactionType.APPROVE_TRANSFER.getValue(), date, savingsAccount.getSummary().getAccountBalance(), + isReversed, isManualTransaction, lienTransaction, refNo); } - public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final AppUser appUser) { + public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office, + final LocalDate date) { final boolean isReversed = false; final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, - SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue(), date, DateUtils.getLocalDateTimeOfSystem(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction, refNo); + SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue(), date, savingsAccount.getSummary().getAccountBalance(), + isReversed, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction withHoldTax(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -308,70 +313,42 @@ public static SavingsAccountTransaction withHoldTax(final SavingsAccount savings final Boolean lienTransaction = false; final String refNo = null; SavingsAccountTransaction accountTransaction = new SavingsAccountTransaction(savingsAccount, office, - SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, null, isManualTransaction, lienTransaction, + SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, isManualTransaction, lienTransaction, refNo); updateTaxDetails(taxDetails, accountTransaction); return accountTransaction; } - public static SavingsAccountTransaction escheat(final SavingsAccount savingsAccount, final LocalDate date, final AppUser appUser, + public static SavingsAccountTransaction escheat(final SavingsAccount savingsAccount, final LocalDate date, final boolean accountTransaction) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; final Boolean lienTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, savingsAccount.office(), paymentDetail, - SavingsAccountTransactionType.ESCHEAT.getValue(), date, DateUtils.getLocalDateTimeOfSystem(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, accountTransaction, lienTransaction, refNo); - } - - public static void updateTaxDetails(final Map taxDetails, - final SavingsAccountTransaction accountTransaction) { - if (taxDetails != null) { - for (Map.Entry mapEntry : taxDetails.entrySet()) { - accountTransaction.getTaxDetails() - .add(new SavingsAccountTransactionTaxDetails(accountTransaction, mapEntry.getKey(), mapEntry.getValue())); - } - } + SavingsAccountTransactionType.ESCHEAT.getValue(), date, savingsAccount.getSummary().getAccountBalance(), isReversed, + accountTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction copyTransaction(SavingsAccountTransaction accountTransaction) { return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office, accountTransaction.paymentDetail, - accountTransaction.typeOf, accountTransaction.transactionLocalDate(), DateUtils.getLocalDateTimeOfSystem(), - accountTransaction.amount, accountTransaction.reversed, accountTransaction.appUser, accountTransaction.isManualTransaction, - accountTransaction.lienTransaction, accountTransaction.refNo); - } - - private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final Integer typeOf, - final LocalDate transactionLocalDate, final Money amount, final boolean isReversed, final AppUser appUser, - final boolean isManualTransaction, final Boolean lienTransaction, final String refNo) { - this(savingsAccount, office, null, typeOf, transactionLocalDate, DateUtils.getLocalDateTimeOfSystem(), amount, isReversed, appUser, - isManualTransaction, lienTransaction, refNo); - } - - private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, - final Integer typeOf, final LocalDate transactionLocalDate, final LocalDateTime createdDate, final Money amount, - final boolean isReversed, final AppUser appUser, final boolean isManualTransaction, final Boolean lienTransaction, - final String refNo) { - this(savingsAccount, office, paymentDetail, typeOf, transactionLocalDate, createdDate, amount.getAmount(), isReversed, appUser, - isManualTransaction, lienTransaction, refNo); + accountTransaction.typeOf, accountTransaction.getTransactionDate(), accountTransaction.amount, accountTransaction.reversed, + accountTransaction.isManualTransaction, accountTransaction.lienTransaction, accountTransaction.refNo); } public static SavingsAccountTransaction holdAmount(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, LocalDateTime createdDate, final AppUser appUser, - final Boolean lienTransaction) { + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, final Boolean lienTransaction) { final boolean isReversed = false; final boolean isManualTransaction = false; final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.AMOUNT_HOLD.getValue(), - date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); + date, amount, isReversed, isManualTransaction, lienTransaction, refNo); } - public static SavingsAccountTransaction releaseAmount(SavingsAccountTransaction accountTransaction, LocalDate transactionDate, - LocalDateTime createdDate, final AppUser appUser) { + public static SavingsAccountTransaction releaseAmount(SavingsAccountTransaction accountTransaction, LocalDate transactionDate) { return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office, accountTransaction.paymentDetail, - SavingsAccountTransactionType.AMOUNT_RELEASE.getValue(), transactionDate, createdDate, accountTransaction.amount, - accountTransaction.reversed, appUser, accountTransaction.isManualTransaction, accountTransaction.lienTransaction, + SavingsAccountTransactionType.AMOUNT_RELEASE.getValue(), transactionDate, accountTransaction.amount, + accountTransaction.reversed, accountTransaction.isManualTransaction, accountTransaction.lienTransaction, accountTransaction.refNo); } @@ -381,25 +358,154 @@ public static SavingsAccountTransaction reversal(SavingsAccountTransaction accou sat.setReversalTransaction(true); sat.originalTxnId = accountTransaction.getId(); return sat; + } + + public static void updateTaxDetails(final Map taxDetails, + final SavingsAccountTransaction accountTransaction) { + if (taxDetails != null) { + for (Map.Entry mapEntry : taxDetails.entrySet()) { + accountTransaction.getTaxDetails() + .add(new SavingsAccountTransactionTaxDetails(accountTransaction, mapEntry.getKey(), mapEntry.getValue())); + } + } + } + public SavingsAccount getSavingsAccount() { + return this.savingsAccount; } - public LocalDate transactionLocalDate() { + public LocalDate getTransactionDate() { return this.dateOf; } + public LocalDate getEndOfBalanceDate() { + return balanceEndDate; + } + + public boolean isReversed() { + return this.reversed; + } + public void reverse() { this.reversed = true; } + public BigDecimal getAmount() { + return this.amount; + } + public Money getAmount(final MonetaryCurrency currency) { return Money.of(currency, this.amount); } + public void setAmount(final Money amount) { + this.amount = amount == null ? null : amount.getAmount(); + } + + public BigDecimal getRunningBalance() { + return this.runningBalance; + } + public Money getRunningBalance(final MonetaryCurrency currency) { return Money.of(currency, this.runningBalance); } + public void setRunningBalance(Money balance) { + this.runningBalance = balance == null ? null : balance.getAmount(); + } + + public boolean isManualTransaction() { + return this.isManualTransaction; + } + + public Set getSavingsAccountChargesPaid() { + return this.savingsAccountChargesPaid; + } + + public BigDecimal getOverdraftAmount() { + return this.overdraftAmount; + } + + public Money getOverdraftAmount(final MonetaryCurrency currency) { + return Money.of(currency, this.overdraftAmount); + } + + void setOverdraftAmount(Money overdraftAmount) { + this.overdraftAmount = overdraftAmount == null ? null : overdraftAmount.getAmount(); + } + + public List getTaxDetails() { + return this.taxDetails; + } + + public List getNotes() { + return notes; + } + + public Integer getTypeOf() { + return this.typeOf; + } + + public SavingsAccountTransactionType getTransactionType() { + return SavingsAccountTransactionType.fromInt(this.typeOf); + } + + public LocalDate getDateOf() { + return this.dateOf; + } + + public String getRefNo() { + return this.refNo; + } + + public PaymentDetail getPaymentDetail() { + return this.paymentDetail; + } + + public void updateReleaseId(Long releaseId) { + this.releaseIdOfHoldAmountTransaction = releaseId; + } + + public void updateReason(String reasonForBlock) { + this.reasonForBlock = reasonForBlock; + } + + public Long getReleaseIdOfHoldAmountTransaction() { + return this.releaseIdOfHoldAmountTransaction; + } + + public MonetaryCurrency getCurrency() { + return savingsAccount == null ? null : savingsAccount.getCurrency(); + } + + public Long getOfficeId() { + return this.office.getId(); + } + + public LocalDate getBalanceEndDate() { + return this.balanceEndDate; + } + + public BigDecimal getCumulativeBalance() { + return this.cumulativeBalance; + } + + public Integer getBalanceNumberOfDays() { + return this.balanceNumberOfDays; + } + + public LocalDate getSubmittedOnDate() { + return submittedOnDate; + } + + public boolean isReversalTransaction() { + return reversalTransaction; + } + + void setReversalTransaction(boolean reversalTransaction) { + this.reversalTransaction = reversalTransaction; + } + public boolean isDeposit() { return getTransactionType().isDeposit(); } @@ -452,10 +558,6 @@ public boolean isNotReversed() { return !isReversed(); } - public boolean isReversed() { - return this.reversed; - } - public boolean isTransferInitiation() { return getTransactionType().isTransferInitiation(); } @@ -476,14 +578,6 @@ public boolean isTransferRelatedTransaction() { return isTransferInitiation() || isTransferApproval() || isTransferRejection() || isTransferWithdrawal(); } - public boolean occursOn(final LocalDate occursOnDate) { - return getTransactionLocalDate().isEqual(occursOnDate); - } - - public void setLoanDisbursement(boolean isLoanDisbursement) { - this.isLoanDisbursement = isLoanDisbursement; - } - public void zeroBalanceFields() { this.runningBalance = null; this.cumulativeBalance = null; @@ -491,35 +585,19 @@ public void zeroBalanceFields() { this.balanceNumberOfDays = null; } - public void updateRunningBalance(final Money balance) { - this.runningBalance = balance.getAmount(); - } - public void updateCumulativeBalanceAndDates(final MonetaryCurrency currency, final LocalDate endOfBalanceDate) { // balance end date should not be before transaction date - if (endOfBalanceDate != null && endOfBalanceDate.isBefore(this.transactionLocalDate())) { - this.balanceEndDate = this.transactionLocalDate(); + if (endOfBalanceDate != null && DateUtils.isBefore(endOfBalanceDate, this.getTransactionDate())) { + this.balanceEndDate = this.getTransactionDate(); } else { this.balanceEndDate = endOfBalanceDate; } - this.balanceNumberOfDays = LocalDateInterval.create(getTransactionLocalDate(), endOfBalanceDate).daysInPeriodInclusiveOfEndDate(); + this.balanceNumberOfDays = LocalDateInterval.create(getTransactionDate(), endOfBalanceDate).daysInPeriodInclusiveOfEndDate(); this.cumulativeBalance = Money.of(currency, this.runningBalance).multipliedBy(this.balanceNumberOfDays).getAmount(); } - public LocalDate getTransactionLocalDate() { - return this.dateOf; - } - - public LocalDate getLastTransactionDate() { - return this.dateOf; - } - - public LocalDate getEndOfBalanceLocalDate() { - return balanceEndDate; - } - public boolean isAcceptableForDailyBalance(final LocalDateInterval interestPeriodInterval) { - return isNotReversed() && interestPeriodInterval.contains(getTransactionLocalDate()) && isABalanceForAtLeastOneDay(); + return isNotReversed() && interestPeriodInterval.contains(getTransactionDate()) && isABalanceForAtLeastOneDay(); } private boolean isABalanceForAtLeastOneDay() { @@ -540,7 +618,7 @@ public Map toMapData(final String currencyCode, final List thisTransactionData.put("officeId", this.office.getId()); thisTransactionData.put("type", transactionType); thisTransactionData.put("reversed", isReversed()); - thisTransactionData.put("date", getTransactionLocalDate()); + thisTransactionData.put("date", getTransactionDate()); thisTransactionData.put("currencyCode", currencyCode); thisTransactionData.put("amount", this.amount); thisTransactionData.put("overdraftAmount", this.overdraftAmount); @@ -585,29 +663,28 @@ public Map toMapData(final String currencyCode, final List return thisTransactionData; } - public boolean isAfter(final LocalDate transactionDate) { - return getTransactionLocalDate().isAfter(transactionDate); + public boolean isBefore(final LocalDate date) { + return DateUtils.isBefore(getTransactionDate(), date); } - public boolean isManualTransaction() { - return this.isManualTransaction; + public boolean isAfter(final LocalDate date) { + return DateUtils.isAfter(getTransactionDate(), date); } - public void setPostInterestAsOn(boolean isManualTransaction) { - this.isManualTransaction = isManualTransaction; + public boolean occursOn(final LocalDate date) { + return DateUtils.isEqual(getTransactionDate(), date); } public EndOfDayBalance toEndOfDayBalance(final LocalDateInterval periodInterval, final MonetaryCurrency currency) { - final Money endOfDayBalance = Money.of(currency, this.runningBalance); final Money openingBalance = endOfDayBalance; LocalDate balanceDate = periodInterval.startDate(); int numberOfDays = periodInterval.daysInPeriodInclusiveOfEndDate(); - if (periodInterval.contains(getTransactionLocalDate())) { - balanceDate = getTransactionLocalDate(); - final LocalDateInterval newInterval = LocalDateInterval.create(getTransactionLocalDate(), periodInterval.endDate()); + if (periodInterval.contains(getTransactionDate())) { + balanceDate = getTransactionDate(); + final LocalDateInterval newInterval = LocalDateInterval.create(getTransactionDate(), periodInterval.endDate()); numberOfDays = newInterval.daysInPeriodInclusiveOfEndDate(); } @@ -615,7 +692,6 @@ public EndOfDayBalance toEndOfDayBalance(final LocalDateInterval periodInterval, } public EndOfDayBalance toEndOfDayBalance(final Money openingBalance, final LocalDate nextTransactionDate) { - final MonetaryCurrency currency = openingBalance.getCurrency(); Money endOfDayBalance = openingBalance.copy(); if (isDeposit() || isDividendPayoutAndNotReversed()) { @@ -624,11 +700,11 @@ public EndOfDayBalance toEndOfDayBalance(final Money openingBalance, final Local endOfDayBalance = openingBalance.minus(getAmount(currency)); } - int numberOfDays = LocalDateInterval.create(getTransactionLocalDate(), nextTransactionDate).daysInPeriodInclusiveOfEndDate(); + int numberOfDays = LocalDateInterval.create(getTransactionDate(), nextTransactionDate).daysInPeriodInclusiveOfEndDate(); if (!openingBalance.isEqualTo(endOfDayBalance) && numberOfDays > 1) { numberOfDays = numberOfDays - 1; } - return EndOfDayBalance.from(getTransactionLocalDate(), openingBalance, endOfDayBalance, numberOfDays); + return EndOfDayBalance.from(getTransactionDate(), openingBalance, endOfDayBalance, numberOfDays); } public EndOfDayBalance toEndOfDayBalance(final Money openingBalance) { @@ -641,24 +717,23 @@ public EndOfDayBalance toEndOfDayBalance(final Money openingBalance) { if (openingBalance.isGreaterThanZero() || this.savingsAccount.allowOverdraft()) { endOfDayBalance = openingBalance.minus(getAmount(currency)); } else { - endOfDayBalance = Money.of(currency, this.runningBalance); + endOfDayBalance = getRunningBalance(currency); } } - return EndOfDayBalance.from(getTransactionLocalDate(), openingBalance, endOfDayBalance, this.balanceNumberOfDays); + return EndOfDayBalance.from(getTransactionDate(), openingBalance, endOfDayBalance, this.balanceNumberOfDays); } public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, final LocalDateInterval boundedBy) { - final MonetaryCurrency currency = openingBalance.getCurrency(); Money endOfDayBalance = openingBalance.copy(); int numberOfDaysOfBalance = this.balanceNumberOfDays; - LocalDate balanceStartDate = getTransactionLocalDate(); - LocalDate balanceEndDate = getEndOfBalanceLocalDate(); + LocalDate balanceStartDate = getTransactionDate(); + LocalDate balanceEndDate = getEndOfBalanceDate(); - if (boundedBy.startDate().isAfter(balanceStartDate)) { + if (DateUtils.isBefore(balanceStartDate, boundedBy.startDate())) { balanceStartDate = boundedBy.startDate(); final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); @@ -673,12 +748,12 @@ public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, fi if (endOfDayBalance.isGreaterThanZero() || this.savingsAccount.allowOverdraft()) { endOfDayBalance = endOfDayBalance.minus(getAmount(currency)); } else { - endOfDayBalance = Money.of(currency, this.runningBalance); + endOfDayBalance = getRunningBalance(currency); } } } - if (balanceEndDate.isAfter(boundedBy.endDate())) { + if (DateUtils.isAfter(balanceEndDate, boundedBy.endDate())) { balanceEndDate = boundedBy.endDate(); final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); @@ -688,16 +763,16 @@ public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, fi } public boolean isBalanceInExistencesForOneDayOrMore() { - return this.balanceNumberOfDays != null && this.balanceNumberOfDays.intValue() >= 1; + return this.balanceNumberOfDays != null && this.balanceNumberOfDays >= 1; } public boolean fallsWithin(final LocalDateInterval periodInterval) { - final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); + final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionDate(), getEndOfBalanceDate()); return periodInterval.contains(balanceInterval); } public boolean spansAnyPortionOf(final LocalDateInterval periodInterval) { - final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); + final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionDate(), getEndOfBalanceDate()); return balanceInterval.containsPortionOf(periodInterval); } @@ -805,123 +880,22 @@ private SavingsAccountChargePaidBy getSavingsAccountChargePaidBy() { return null; } - public Set getSavingsAccountChargesPaid() { - return this.savingsAccountChargesPaid; - } - - public void updateOverdraftAmount(BigDecimal overdraftAmount) { - this.overdraftAmount = overdraftAmount; - } - - public Money getOverdraftAmount(final MonetaryCurrency currency) { - return Money.of(currency, this.overdraftAmount); - } - public boolean isPaymentForCurrentCharge(final SavingsAccountCharge savingsAccountCharge) { final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); return chargePaidBy != null && chargePaidBy.getSavingsAccountCharge().equals(savingsAccountCharge); } - public BigDecimal getAmount() { - return this.amount; - } - - public List getTaxDetails() { - return this.taxDetails; - } - - public List getNotes() { - return notes; - } - - public void updateAmount(final Money amount) { - this.amount = amount.getAmount(); - } - - public Integer getTypeOf() { - return this.typeOf; - } - - public SavingsAccountTransactionType getTransactionType() { - return SavingsAccountTransactionType.fromInt(this.typeOf); - } - - public SavingsAccount getSavingsAccount() { - return this.savingsAccount; - } - - public void setSavingsAccount(SavingsAccount savingsAccount) { - this.savingsAccount = savingsAccount; - } - - public LocalDate getDateOf() { - return this.dateOf; - } - - public String getRefNo() { - return this.refNo; - } - - public PaymentDetail getPaymentDetail() { - return this.paymentDetail; - } - - public void updateReleaseId(Long releaseId) { - this.releaseIdOfHoldAmountTransaction = releaseId; - } - - public void updateReason(String reasonForBlock) { - this.reasonForBlock = reasonForBlock; - } - - public Long getReleaseIdOfHoldAmountTransaction() { - return this.releaseIdOfHoldAmountTransaction; + @Override + public Optional getCreatedDate() { + // #audit backward compatibility keep system datetime + return Optional.ofNullable(super.getCreatedDate() + .orElse(createdDateToRemove == null ? null : createdDateToRemove.atZone(getSystemZoneId()).toOffsetDateTime())); } public boolean isAmountOnHoldNotReleased() { return (isAmountOnHold() && getReleaseIdOfHoldAmountTransaction() == null); } - public Long getOfficeId() { - return this.office.getId(); - } - - public LocalDate getBalanceEndDate() { - return this.balanceEndDate; - } - - public BigDecimal getCumulativeBalance() { - return this.cumulativeBalance; - } - - public Integer getBalanceNumberOfDays() { - return this.balanceNumberOfDays; - } - - public Long getAppUserId() { - return this.appUser.getId(); - } - - public LocalDateTime getCreatedDate() { - return this.createdDate; - } - - public LocalDate getSubmittedOnDate() { - return submittedOnDate; - } - - public boolean getIsManualTransaction() { - return this.isManualTransaction; - } - - public void setReversalTransaction(boolean reversalTransaction) { - this.reversalTransaction = reversalTransaction; - } - - public boolean isReversalTransaction() { - return reversalTransaction; - } - public SavingsAccountTransactionDetailsForPostingPeriod toSavingsAccountTransactionDetailsForPostingPeriod(MonetaryCurrency currency, boolean isAllowOverDraft) { return new SavingsAccountTransactionDetailsForPostingPeriod(getId(), this.dateOf, this.balanceEndDate, this.runningBalance, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionComparator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionComparator.java index 3527db7fe38..d526246c573 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionComparator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionComparator.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.savings.domain; import java.util.Comparator; +import org.apache.fineract.infrastructure.core.service.DateUtils; /** * Sort savings account transactions by transaction date and transaction type placing @@ -27,18 +28,17 @@ public class SavingsAccountTransactionComparator implements Comparator { @Override public int compare(final SavingsAccountTransactionData o1, final SavingsAccountTransactionData o2) { - int compareResult = 0; - - final int comparsion = o1.getTransactionDate().compareTo(o2.getTransactionDate()); - if (comparsion == 0) { - compareResult = o1.getSubmittedOnDate().compareTo(o2.getSubmittedOnDate()); - if (compareResult == 0 && o1.getId() != null && o2.getId() != null) { - compareResult = o1.getId().compareTo(o2.getId()); - } else { - compareResult = comparsion; - } - } else { - compareResult = comparsion; + int result = DateUtils.compare(o1.getTransactionDate(), o2.getTransactionDate()); + if (result != 0) { + return result; + } + result = DateUtils.compare(o1.getSubmittedOnDate(), o2.getSubmittedOnDate()); + if (result != 0) { + return result; + } + if (o1.getId() != null && o2.getId() != null) { + return o1.getId().compareTo(o2.getId()); } - return compareResult; + return 0; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java index 995bd3d521a..6060339d9fb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java @@ -18,18 +18,24 @@ */ package org.apache.fineract.portfolio.savings.domain; +import static org.apache.fineract.infrastructure.core.service.DateUtils.getSystemZoneId; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import java.time.LocalDate; -import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.Optional; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.staff.domain.Staff; @Entity @Table(name = "m_savings_officer_assignment_history") -public class SavingsOfficerAssignmentHistory extends AbstractAuditableCustom { +public class SavingsOfficerAssignmentHistory extends AbstractAuditableWithUTCDateTimeCustom { @ManyToOne @JoinColumn(name = "account_id", nullable = false) @@ -45,10 +51,13 @@ public class SavingsOfficerAssignmentHistory extends AbstractAuditableCustom { @Column(name = "end_date") private LocalDate endDate; - public static SavingsOfficerAssignmentHistory createNew(final SavingsAccount account, final Staff savingsOfficer, - final LocalDate assignmentDate) { - return new SavingsOfficerAssignmentHistory(account, savingsOfficer, assignmentDate, null); - } + @Deprecated + @Column(name = "created_date") + private LocalDateTime createdDateToRemove; + + @Deprecated + @Column(name = "lastmodified_date") + private LocalDateTime lastModifiedDateToRemove; protected SavingsOfficerAssignmentHistory() { // @@ -62,7 +71,12 @@ private SavingsOfficerAssignmentHistory(final SavingsAccount account, final Staf this.endDate = endDate; } - public void updateSavingsOfficer(final Staff savingsOfficer) { + public static SavingsOfficerAssignmentHistory createNew(final SavingsAccount account, final Staff savingsOfficer, + final LocalDate startDate) { + return new SavingsOfficerAssignmentHistory(account, savingsOfficer, startDate, null); + } + + public void setSavingsOfficer(final Staff savingsOfficer) { this.savingsOfficer = savingsOfficer; } @@ -70,28 +84,28 @@ public boolean isSameSavingsOfficer(final Staff staff) { return this.savingsOfficer.identifiedBy(staff); } - public void updateStartDate(final LocalDate startDate) { - this.startDate = startDate; + public LocalDate getStartDate() { + return this.startDate; } - public void updateEndDate(final LocalDate endDate) { - this.endDate = endDate; + public void setStartDate(final LocalDate startDate) { + this.startDate = startDate; } - public boolean matchesStartDateOf(final LocalDate matchingDate) { - return getStartDate().isEqual(matchingDate); + public LocalDate getEndDate() { + return this.endDate; } - public LocalDate getStartDate() { - return this.startDate; + public void setEndDate(final LocalDate endDate) { + this.endDate = endDate; } - public boolean hasStartDateBefore(final LocalDate matchingDate) { - return matchingDate.isBefore(getStartDate()); + public boolean matchesStartDateOf(final LocalDate matchingDate) { + return DateUtils.isEqual(matchingDate, getStartDate()); } - public boolean isCurrentRecord() { - return this.endDate == null; + public boolean isBeforeStartDate(final LocalDate matchingDate) { + return DateUtils.isBefore(matchingDate, getStartDate()); } /** @@ -100,12 +114,25 @@ public boolean isCurrentRecord() { * @param compareDate * @return */ - public boolean isEndDateAfter(final LocalDate compareDate) { - return this.endDate != null && this.endDate.isAfter(compareDate); + public boolean isBeforeEndDate(final LocalDate compareDate) { + return DateUtils.isBefore(compareDate, this.endDate); } - public LocalDate getEndDate() { - return this.endDate; + public boolean isCurrentRecord() { + return this.endDate == null; } + @Override + public Optional getCreatedDate() { + // #audit backward compatibility keep system datetime + return Optional.ofNullable(super.getCreatedDate() + .orElse(createdDateToRemove == null ? null : createdDateToRemove.atZone(getSystemZoneId()).toOffsetDateTime())); + } + + @Override + public Optional getLastModifiedDate() { + // #audit backward compatibility keep system datetime + return Optional.ofNullable(super.getLastModifiedDate() + .orElse(lastModifiedDateToRemove == null ? null : lastModifiedDateToRemove.atZone(getSystemZoneId()).toOffsetDateTime())); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java index c976cde5fbc..50b4878ee9e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java @@ -82,7 +82,6 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventView; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.charge.domain.Charge; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountBlockedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountBlockedException.java index 50f7d4103bd..d934705187c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountBlockedException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountBlockedException.java @@ -26,4 +26,9 @@ public SavingsAccountBlockedException(final Long accountId) { super("error.msg.saving.account.blocked.transaction.not.allowed", "Any transaction to " + accountId + " is not allowed, since the account is blocked", accountId); } + + public SavingsAccountBlockedException(final String accountNo) { + super("error.msg.saving.account.blocked.transaction.not.allowed", + "Any transaction to " + accountNo + " is not allowed, since the account is blocked", accountNo); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountCreditsBlockedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountCreditsBlockedException.java index d54cb87745f..c4e1647925a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountCreditsBlockedException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountCreditsBlockedException.java @@ -26,4 +26,9 @@ public SavingsAccountCreditsBlockedException(final Long accountId) { super("error.msg.savings.account.credit.transaction.not.allowed", "Any Credit transactions to " + accountId + " is not allowed, since the account is blocked for credits", accountId); } + + public SavingsAccountCreditsBlockedException(final String accountNo) { + super("error.msg.savings.account.credit.transaction.not.allowed", + "Any Credit transactions to " + accountNo + " is not allowed, since the account is blocked for credits", accountNo); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountDebitsBlockedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountDebitsBlockedException.java index 8a2903b4ddf..412c547eae4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountDebitsBlockedException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountDebitsBlockedException.java @@ -27,4 +27,8 @@ public SavingsAccountDebitsBlockedException(final Long accountId) { "Any debit transactions from " + accountId + " is not allowed, since the account is blocked for debits", accountId); } + public SavingsAccountDebitsBlockedException(final String accountNo) { + super("error.msg.savings.account.debit.transaction.not.allowed", + "Any debit transactions from " + accountNo + " is not allowed, since the account is blocked for debits", accountNo); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoActivationFixedDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoActivationFixedDepositAccountCommandHandler.java index 1c383fdf699..f6fbf339c0f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoActivationFixedDepositAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoActivationFixedDepositAccountCommandHandler.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.savings.handler; +import lombok.RequiredArgsConstructor; import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -26,8 +27,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; - @RequiredArgsConstructor @Service @CommandType(entity = "FIXEDDEPOSITACCOUNT", action = "UNDO_ACTIVATE") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsConfig.java index e825ac3250b..9e437ff3a12 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsConfig.java @@ -52,8 +52,8 @@ protected Step addAccrualTransactionForSavingsStep() { @Bean public Job addAccrualTransactionForSavingsJob() { - return new JobBuilder(JobName.ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS.name(), jobRepository).start(addAccrualTransactionForSavingsStep()) - .incrementer(new RunIdIncrementer()).build(); + return new JobBuilder(JobName.ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS.name(), jobRepository) + .start(addAccrualTransactionForSavingsStep()).incrementer(new RunIdIncrementer()).build(); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsTasklet.java index 7f705edb6cf..05ae03163e9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsTasklet.java @@ -51,7 +51,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon } catch (final PlatformApiDataValidationException e) { final List errors = e.getErrors(); for (final ApiParameterError error : errors) { - log.error("Add Accrual Transaction failed for account: {} with message {}", savingsAccountReference.getAccountNo(), error); + log.error("Add Accrual Transaction failed for account: {} with message {}", savingsAccountReference.getAccountNo(), + error); } } catch (final Exception ex) { log.error("Add Accrual Transaction failed for account: {}", savingsAccountReference.getAccountNo(), ex); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generaterdschedule/GenerateRdScheduleConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generaterdschedule/GenerateRdScheduleConfig.java index d32db1c09e6..64cc452ce31 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generaterdschedule/GenerateRdScheduleConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generaterdschedule/GenerateRdScheduleConfig.java @@ -20,6 +20,7 @@ import org.apache.fineract.infrastructure.core.service.database.RoutingDataSourceServiceFactory; import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; @@ -43,6 +44,8 @@ public class GenerateRdScheduleConfig { private RoutingDataSourceServiceFactory dataSourceServiceFactory; @Autowired private DepositAccountReadPlatformService depositAccountReadPlatformService; + @Autowired + private PlatformSecurityContext securityContext; @Bean protected Step generateRdScheduleStep() { @@ -58,6 +61,6 @@ public Job generateRdScheduleJob() { @Bean public GenerateRdScheduleTasklet generateRdScheduleTasklet() { - return new GenerateRdScheduleTasklet(dataSourceServiceFactory, depositAccountReadPlatformService); + return new GenerateRdScheduleTasklet(dataSourceServiceFactory, depositAccountReadPlatformService, securityContext); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generaterdschedule/GenerateRdScheduleTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generaterdschedule/GenerateRdScheduleTasklet.java index a555afd0466..e49ffe3ce5e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generaterdschedule/GenerateRdScheduleTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generaterdschedule/GenerateRdScheduleTasklet.java @@ -18,15 +18,23 @@ */ package org.apache.fineract.portfolio.savings.jobs.generaterdschedule; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_BY_DB_FIELD; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_DATE_DB_FIELD; + import java.math.BigDecimal; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; +import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.database.RoutingDataSourceServiceFactory; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.savings.DepositAccountUtils; import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService; import org.springframework.batch.core.StepContribution; @@ -41,19 +49,20 @@ public class GenerateRdScheduleTasklet implements Tasklet { private final RoutingDataSourceServiceFactory dataSourceServiceFactory; private final DepositAccountReadPlatformService depositAccountReadPlatformService; - private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - private final DateTimeFormatter formatterWithTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private final PlatformSecurityContext securityContext; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceServiceFactory.determineDataSourceService().retrieveDataSource()); final Collection> scheduleDetails = depositAccountReadPlatformService.retriveDataForRDScheduleCreation(); - String insertSql = "INSERT INTO m_mandatory_savings_schedule (savings_account_id, duedate, installment, deposit_amount, completed_derived, created_date, lastmodified_date) VALUES "; - StringBuilder sb = new StringBuilder(); - String currentDate = DateUtils.getLocalDateTimeOfTenant().format(DateUtils.DEFAULT_DATETIME_FORMATTER); + String insertSql = "INSERT INTO m_mandatory_savings_schedule (savings_account_id, duedate, installment, deposit_amount, completed_derived, " + + CREATED_DATE_DB_FIELD + ", " + CREATED_BY_DB_FIELD + ", " + LAST_MODIFIED_DATE_DB_FIELD + ", " + LAST_MODIFIED_BY_DB_FIELD + + ") " + "VALUES (?, ?, ?, ?, ?, ?, ?)"; + List params = new ArrayList<>(); + Long userId = securityContext.authenticatedUser().getId(); int iterations = 0; for (Map details : scheduleDetails) { - Long count = (Long) details.get("futureInstallemts"); + Long count = (Long) details.get("futureInstallments"); if (count == null) { count = 0L; } @@ -66,34 +75,20 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon count++; installmentNumber++; lastDepositDate = DepositAccountUtils.calculateNextDepositDate(lastDepositDate, recurrence); - - if (sb.length() > 0) { - sb.append(", "); - } - sb.append("("); - sb.append(savingsId); - sb.append(",'"); - sb.append(formatter.format(lastDepositDate)); - sb.append("',"); - sb.append(installmentNumber); - sb.append(","); - sb.append(amount); - sb.append(", b'0','"); - sb.append(currentDate); - sb.append("','"); - sb.append(currentDate); - sb.append("')"); + OffsetDateTime auditTime = DateUtils.getAuditOffsetDateTime(); + params.add(new Object[] { savingsId, lastDepositDate, installmentNumber, amount, false, auditTime, userId, auditTime, + userId }); iterations++; - if (iterations > 200) { - jdbcTemplate.update(insertSql + sb); // NOSONAR - sb = new StringBuilder(); - } - + } + if (iterations > 200) { + jdbcTemplate.batchUpdate(insertSql, params); + params.clear(); + iterations = 0; } } - if (sb.length() > 0) { - jdbcTemplate.update(insertSql + sb); // NOSONAR + if (!params.isEmpty()) { + jdbcTemplate.batchUpdate(insertSql, params); } return RepeatStatus.FINISHED; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index c9698b5ad15..6935606854a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.savings.service; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD; + import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; @@ -524,7 +526,7 @@ public Collection> retriveDataForRDScheduleCreation() { sb.append(" select rd.savings_account_id savingsId, rd.mandatory_recommended_deposit_amount as amount,"); sb.append(" mc.recurrence as recurrence ,"); sb.append(" max(ms.duedate) as dueDate , max(ms.installment) as installment,"); - sb.append(" count(ms.installment) as futureInstallemts"); + sb.append(" count(ms.installment) as futureInstallments"); sb.append(" from m_deposit_account_term_and_preclosure dat "); sb.append(" inner join m_savings_account sa on sa.id = dat.savings_account_id and sa.status_enum = ?"); sb.append(" inner join m_deposit_account_recurring_detail rd on rd.savings_account_id = dat.savings_account_id "); @@ -1163,7 +1165,7 @@ private static final class SavingsAccountTransactionsMapper implements RowMapper sqlBuilder.append("left join m_account_transfer_transaction totran on totran.to_savings_transaction_id = tr.id "); sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id "); sqlBuilder.append("left join m_payment_type pt on pd.payment_type_id = pt.id "); - sqlBuilder.append("left join m_appuser au on au.id=tr.appuser_id "); + sqlBuilder.append("left join m_appuser au on au.id = tr." + CREATED_BY_DB_FIELD); this.schemaSql = sqlBuilder.toString(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index 867fea64ffb..e189b7c194a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -143,8 +143,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo @Transactional @Override public CommandProcessingResult activateFDAccount(final Long savingsId, final JsonCommand command) { - - final AppUser user = this.context.authenticatedUser(); + final AppUser user = context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -160,7 +159,7 @@ public CommandProcessingResult activateFDAccount(final Long savingsId, final Jso final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - final Map changes = account.activate(user, command, DateUtils.getBusinessLocalDate()); + final Map changes = account.activate(user, command); Money activationChargeAmount = getActivationCharge(account); if (!changes.isEmpty()) { final Locale locale = command.extractLocale(); @@ -172,13 +171,13 @@ public CommandProcessingResult activateFDAccount(final Long savingsId, final Jso if (portfolioAccountData == null) { final PaymentDetail paymentDetail = null; - this.depositAccountDomainService.handleFDDeposit(account, fmt, account.getActivationLocalDate(), + this.depositAccountDomainService.handleFDDeposit(account, fmt, account.getActivationDate(), amountForDeposit.getAmount(), paymentDetail); } else { final SavingsAccount fromSavingsAccount = null; boolean isRegularTransaction = false; final boolean isExceptionForBalanceCheck = false; - final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(), + final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationDate(), amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, portfolioAccountData.getId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null, account, @@ -188,14 +187,15 @@ public CommandProcessingResult activateFDAccount(final Long savingsId, final Jso final boolean isInterestTransfer = false; final LocalDate postInterestOnDate = null; if (activationChargeAmount.isGreaterThanZero()) { - payActivationCharge(account, user); + payActivationCharge(account); } - if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate(), false)) { + if (account.isBeforeLastPostingPeriod(account.getActivationDate(), false)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, + postReversals); // postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - // financialYearBeginningMonth, postInterestOnDate, false); + // financialYearBeginningMonth, postInterestOnDate, false); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -254,7 +254,7 @@ public CommandProcessingResult undoActivateFDAccount(Long savingsId, JsonCommand .withSavingsId(savingsId) // .with(changes) // .build(); - } + } private Money getActivationCharge(final FixedDepositAccount account) { Money activationChargeAmount = Money.zero(account.getCurrency()); @@ -266,11 +266,11 @@ private Money getActivationCharge(final FixedDepositAccount account) { return activationChargeAmount; } - private void payActivationCharge(final FixedDepositAccount account, AppUser user) { + private void payActivationCharge(final FixedDepositAccount account) { for (SavingsAccountCharge savingsAccountCharge : account.charges()) { if (savingsAccountCharge.isSavingsActivation()) { - account.payCharge(savingsAccountCharge, savingsAccountCharge.getAmount(account.getCurrency()), - account.getActivationLocalDate(), user, false, null); + account.payCharge(savingsAccountCharge, savingsAccountCharge.getAmount(account.getCurrency()), account.getActivationDate(), + false, null); } } } @@ -296,7 +296,7 @@ public CommandProcessingResult activateRDAccount(final Long savingsId, final Jso final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - final Map changes = account.activate(user, command, DateUtils.getBusinessLocalDate()); + final Map changes = account.activate(user, command); if (!changes.isEmpty()) { final Locale locale = command.extractLocale(); @@ -306,12 +306,12 @@ public CommandProcessingResult activateRDAccount(final Long savingsId, final Jso final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService .retriveSavingsLinkedAssociation(savingsId); if (portfolioAccountData == null) { - this.depositAccountDomainService.handleRDDeposit(account, fmt, account.getActivationLocalDate(), + this.depositAccountDomainService.handleRDDeposit(account, fmt, account.getActivationDate(), amountForDeposit.getAmount(), null, isRegularTransaction); } else { final boolean isExceptionForBalanceCheck = false; final SavingsAccount fromSavingsAccount = null; - final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(), + final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationDate(), amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, portfolioAccountData.getId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null, account, @@ -343,10 +343,10 @@ public CommandProcessingResult activateRDAccount(final Long savingsId, final Jso account.updateOverduePayments(overdueUptoDate); final boolean isInterestTransfer = false; final LocalDate postInterestOnDate = null; - if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate(), false)) { + if (account.isBeforeLastPostingPeriod(account.getActivationDate(), false)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -547,8 +547,8 @@ private void postInterest(final SavingsAccount account) { boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final boolean postReversals = false; - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, false, postReversals); this.savingAccountRepositoryWrapper.saveAndFlush(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); @@ -600,9 +600,9 @@ public CommandProcessingResult undoRDTransaction(final Long savingsId, final Lon checkClientOrGroupActive(account); final boolean postReversals = false; if (savingsAccountTransaction.isPostInterestCalculationRequired() - && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + && account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), false)) { + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, postReversals); @@ -649,8 +649,7 @@ public CommandProcessingResult adjustFDTransaction(final Long savingsId, @Suppre @Override public CommandProcessingResult adjustRDTransaction(final Long savingsId, final Long transactionId, final JsonCommand command) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -701,11 +700,11 @@ public CommandProcessingResult adjustRDTransaction(final Long savingsId, final L UUID refNo = UUID.randomUUID(); if (savingsAccountTransaction.isDeposit()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, savingsAccountTransaction.getCreatedDate(), user, accountType); + paymentDetail, null, accountType); transaction = account.deposit(transactionDTO, false, relaxingDaysConfigForPivotDate, refNo.toString()); } else { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, savingsAccountTransaction.getCreatedDate(), user, accountType); + paymentDetail, null, accountType); transaction = account.withdraw(transactionDTO, true, false, relaxingDaysConfigForPivotDate, refNo.toString()); } final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); @@ -713,9 +712,9 @@ public CommandProcessingResult adjustRDTransaction(final Long savingsId, final L final LocalDate postInterestOnDate = null; final boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, false) - || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + || account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), false)) { + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, postReversals); @@ -787,8 +786,7 @@ public CommandProcessingResult closeFDAccount(final Long savingsId, final JsonCo DepositAccountType.FIXED_DEPOSIT); checkClientOrGroupActive(account); - this.depositAccountDomainService.handleFDAccountClosure(account, paymentDetail, user, command, DateUtils.getBusinessLocalDate(), - changes); + this.depositAccountDomainService.handleFDAccountClosure(account, paymentDetail, user, command, changes); final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { @@ -821,8 +819,7 @@ public CommandProcessingResult closeRDAccount(final Long savingsId, final JsonCo DepositAccountType.RECURRING_DEPOSIT); checkClientOrGroupActive(account); - this.depositAccountDomainService.handleRDAccountClosure(account, paymentDetail, user, command, DateUtils.getBusinessLocalDate(), - changes); + this.depositAccountDomainService.handleRDAccountClosure(account, paymentDetail, user, command, changes); final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { @@ -855,8 +852,7 @@ public CommandProcessingResult prematureCloseFDAccount(final Long savingsId, fin DepositAccountType.FIXED_DEPOSIT); checkClientOrGroupActive(account); - this.depositAccountDomainService.handleFDAccountPreMatureClosure(account, paymentDetail, user, command, - DateUtils.getBusinessLocalDate(), changes); + this.depositAccountDomainService.handleFDAccountPreMatureClosure(account, paymentDetail, user, command, changes); final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { @@ -898,8 +894,7 @@ public CommandProcessingResult prematureCloseRDAccount(final Long savingsId, fin } } - this.depositAccountDomainService.handleRDAccountPreMatureClosure(account, paymentDetail, user, command, - DateUtils.getBusinessLocalDate(), changes); + this.depositAccountDomainService.handleRDAccountPreMatureClosure(account, paymentDetail, user, command, changes); final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { @@ -922,8 +917,7 @@ public CommandProcessingResult prematureCloseRDAccount(final Long savingsId, fin @Override public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, final LocalDate transferDate, final DepositAccountType depositAccountType) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -935,7 +929,7 @@ public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, f updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction newTransferTransaction = SavingsAccountTransaction.initiateTransfer(savingsAccount, - savingsAccount.office(), transferDate, user); + savingsAccount.office(), transferDate); savingsAccount.addTransaction(newTransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue()); final MathContext mc = MathContext.DECIMAL64; @@ -955,8 +949,7 @@ public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, f @Override public SavingsAccountTransaction withdrawSavingsTransfer(final Long accountId, final LocalDate transferDate, final DepositAccountType depositAccountType) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -969,7 +962,7 @@ public SavingsAccountTransaction withdrawSavingsTransfer(final Long accountId, f updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction withdrawtransferTransaction = SavingsAccountTransaction.withdrawTransfer(savingsAccount, - savingsAccount.office(), transferDate, user); + savingsAccount.office(), transferDate); savingsAccount.addTransaction(withdrawtransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); final boolean postReversals = false; @@ -997,8 +990,7 @@ public void rejectSavingsTransfer(final Long accountId, final DepositAccountType @Override public SavingsAccountTransaction acceptSavingsTransfer(final Long accountId, final LocalDate transferDate, final Office acceptedInOffice, final Staff fieldOfficer, final DepositAccountType depositAccountType) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1011,7 +1003,7 @@ public SavingsAccountTransaction acceptSavingsTransfer(final Long accountId, fin updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction acceptTransferTransaction = SavingsAccountTransaction.approveTransfer(savingsAccount, - acceptedInOffice, transferDate, user); + acceptedInOffice, transferDate); savingsAccount.addTransaction(acceptTransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); if (fieldOfficer != null) { @@ -1057,11 +1049,11 @@ public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command); - if (savingsAccountCharge.getDueLocalDate() != null) { + if (savingsAccountCharge.getDueDate() != null) { // transaction date should not be on a holiday or non working day if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled() - && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().format(fmt)) + && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueDate().format(fmt)) .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); @@ -1069,8 +1061,8 @@ public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command } if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled() - && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().format(fmt)) + && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueDate().format(fmt)) .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); @@ -1113,14 +1105,14 @@ public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand comm final Map changes = savingsAccountCharge.update(command); - if (savingsAccountCharge.getDueLocalDate() != null) { + if (savingsAccountCharge.getDueDate() != null) { final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); // transaction date should not be on a holiday or non working day if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled() - && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().format(fmt)) + && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueDate().format(fmt)) .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); @@ -1128,8 +1120,8 @@ public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand comm } if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled() - && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().format(fmt)) + && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueDate().format(fmt)) .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); @@ -1153,8 +1145,7 @@ public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand comm @Override public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId, @SuppressWarnings("unused") final DepositAccountType depositAccountType) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1171,15 +1162,15 @@ public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Lo final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.waiveCharge(savingsAccountChargeId, user, false); + account.waiveCharge(savingsAccountChargeId, false); boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; final boolean postReversals = false; - if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), false)) { + if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueDate(), false)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -1283,14 +1274,13 @@ public CommandProcessingResult payCharge(final Long savingsAccountId, final Long @Override public void applyChargeDue(final Long savingsAccountChargeId, final Long accountId, @SuppressWarnings("unused") final DepositAccountType depositAccountType) { - // always use current date as transaction date for batch job - final LocalDate transactionDate = DateUtils.getBusinessLocalDate(); final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository .findOneWithNotFoundDetection(savingsAccountChargeId, accountId); - + // always use current date as transaction date for batch job + final LocalDate transactionDate = DateUtils.getBusinessLocalDate(); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MM yyyy"); - while (transactionDate.isAfter(savingsAccountCharge.getDueLocalDate())) { + while (DateUtils.isBefore(savingsAccountCharge.getDueDate(), transactionDate)) { payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt); } } @@ -1298,8 +1288,7 @@ public void applyChargeDue(final Long savingsAccountChargeId, final Long account @Transactional private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid, final DateTimeFormatter formatter) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1311,15 +1300,15 @@ private void payCharge(final SavingsAccountCharge savingsAccountCharge, final Lo final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user, false, null); + account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, false, null); boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; final boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, false)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -1342,7 +1331,6 @@ private void payCharge(final SavingsAccountCharge savingsAccountCharge, final Lo @Transactional @Override public void updateMaturityDetails(Long depositAccountId, DepositAccountType depositAccountType) { - final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -1360,10 +1348,10 @@ public void updateMaturityDetails(Long depositAccountId, DepositAccountType depo if (fdAccount.isMatured() && (fdAccount.isReinvestOnClosure() || fdAccount.isTransferToSavingsOnClosure())) { DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); Map changes = new HashMap<>(); - AppUser user = context.authenticatedUser(); + final AppUser user = context.authenticatedUser(); Long toSavingsId = fdAccount.getTransferToSavingsAccountId(); - this.depositAccountDomainService.handleFDAccountMaturityClosure(fdAccount, null, user, fdAccount.maturityDate(), fmt, - fdAccount.maturityDate(), fdAccount.getOnAccountClosureId(), toSavingsId, "Apply maturity instructions", changes); + this.depositAccountDomainService.handleFDAccountMaturityClosure(fdAccount, null, user, fmt, fdAccount.maturityDate(), + fdAccount.getOnAccountClosureId(), toSavingsId, "Apply maturity instructions", changes); if (changes.get("reinvestedDepositId") != null) { Long reinvestedDepositId = (Long) changes.get("reinvestedDepositId"); @@ -1372,7 +1360,7 @@ public void updateMaturityDetails(Long depositAccountId, DepositAccountType depo .assembleFrom(reinvestedDepositId, DepositAccountType.FIXED_DEPOSIT); Money activationChargeAmount = getActivationCharge(reinvestAccount); if (activationChargeAmount.isGreaterThanZero()) { - payActivationCharge(reinvestAccount, user); + payActivationCharge(reinvestAccount); amountForDeposit = amountForDeposit.plus(activationChargeAmount); } this.depositAccountDomainService.handleFDDeposit(reinvestAccount, fmt, fdAccount.maturityDate(), @@ -1430,13 +1418,4 @@ public SavingsAccountTransaction mandatorySavingsAccountDeposit(final SavingsAcc isRegularTransaction); } - - private AppUser getAppUserIfPresent() { - AppUser user = null; - if (this.context != null) { - user = this.context.getAuthenticatedUserIfPresent(); - } - return user; - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index 089e0daf4d5..af393f0ace6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -47,7 +47,6 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.event.business.domain.deposit.FixedDepositAccountCreateBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.deposit.RecurringDepositAccountCreateBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; @@ -384,7 +383,7 @@ public CommandProcessingResult modifyFDApplication(final Long accountId, final J DepositAccountType.FIXED_DEPOSIT); checkClientOrGroupActive(account); account.modifyApplication(command, changes); - account.validateNewApplicationState(DateUtils.getBusinessLocalDate(), DepositAccountType.FIXED_DEPOSIT.resourceName()); + account.validateNewApplicationState(DepositAccountType.FIXED_DEPOSIT.resourceName()); if (!changes.isEmpty()) { updateFDAndRDCommonChanges(changes, command, account); @@ -450,11 +449,11 @@ public CommandProcessingResult modifyFDApplication(final Long accountId, final J .build(); } catch (final DataAccessException dve) { handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); - return new CommandProcessingResult((long) -1); + return CommandProcessingResult.resourceResult(-1L); } catch (final PersistenceException dve) { Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); handleDataIntegrityIssues(command, throwable, dve); - return new CommandProcessingResult((long) -1); + return CommandProcessingResult.resourceResult(-1L); } } @@ -474,7 +473,7 @@ public CommandProcessingResult modifyRDApplication(final Long accountId, final J DepositAccountType.RECURRING_DEPOSIT); checkClientOrGroupActive(account); account.modifyApplication(command, changes); - account.validateNewApplicationState(DateUtils.getBusinessLocalDate(), DepositAccountType.RECURRING_DEPOSIT.resourceName()); + account.validateNewApplicationState(DepositAccountType.RECURRING_DEPOSIT.resourceName()); if (!changes.isEmpty()) { updateFDAndRDCommonChanges(changes, command, account); @@ -521,11 +520,11 @@ public CommandProcessingResult modifyRDApplication(final Long accountId, final J .build(); } catch (final DataAccessException dve) { handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); - return new CommandProcessingResult(Long.valueOf(-1)); + return CommandProcessingResult.resourceResult(-1L); } catch (final PersistenceException dve) { Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); handleDataIntegrityIssues(command, throwable, dve); - return new CommandProcessingResult(Long.valueOf(-1)); + return CommandProcessingResult.resourceResult(-1L); } } @@ -638,7 +637,7 @@ public CommandProcessingResult approveApplication(final Long savingsId, final Js final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType); checkClientOrGroupActive(savingsAccount); - final Map changes = savingsAccount.approveApplication(currentUser, command, DateUtils.getBusinessLocalDate()); + final Map changes = savingsAccount.approveApplication(currentUser, command); if (!changes.isEmpty()) { this.savingAccountRepository.save(savingsAccount); @@ -708,7 +707,7 @@ public CommandProcessingResult rejectApplication(final Long savingsId, final Jso final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType); checkClientOrGroupActive(savingsAccount); - final Map changes = savingsAccount.rejectApplication(currentUser, command, DateUtils.getBusinessLocalDate()); + final Map changes = savingsAccount.rejectApplication(currentUser, command); if (!changes.isEmpty()) { this.savingAccountRepository.save(savingsAccount); @@ -742,8 +741,7 @@ public CommandProcessingResult applicantWithdrawsFromApplication(final Long savi final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsId, depositAccountType); checkClientOrGroupActive(savingsAccount); - final Map changes = savingsAccount.applicantWithdrawsFromApplication(currentUser, command, - DateUtils.getBusinessLocalDate()); + final Map changes = savingsAccount.applicantWithdrawsFromApplication(currentUser, command); if (!changes.isEmpty()) { this.savingAccountRepository.save(savingsAccount); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java index 4e10ca9d160..7bb15e9ece7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java @@ -94,7 +94,6 @@ public String schema() { @Override public SavingsAccountChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - final Long id = rs.getLong("id"); final Long chargeId = rs.getLong("chargeId"); final Long accountId = rs.getLong("accountId"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java index f1a33364b5c..9ddc2fba943 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.savings.service; +import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; @@ -29,7 +30,9 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.savings.DepositAccountType; @@ -47,8 +50,6 @@ import org.apache.fineract.portfolio.tax.service.TaxUtils; import org.springframework.stereotype.Service; -import lombok.RequiredArgsConstructor; - @Service @RequiredArgsConstructor public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountInterestPostingService { @@ -59,7 +60,6 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI public SavingsAccountData postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final SavingsAccountData savingsAccountData) { - Money interestPostedToDate = Money.zero(savingsAccountData.getCurrency()); LocalDate startInterestDate = getStartInterestCalculationDate(savingsAccountData); @@ -81,13 +81,11 @@ public SavingsAccountData postInterest(final MathContext mc, final LocalDate int withholdTransactions.addAll(findWithHoldSavingsTransactionsWithPivotConfig(savingsAccountData)); for (final PostingPeriod interestPostingPeriod : postingPeriods) { - final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); - if (!interestPostingTransactionDate.isAfter(interestPostingUpToDate)) { + if (!DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate)) { interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); - final SavingsAccountTransactionData postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate, savingsAccountData); @@ -143,8 +141,9 @@ public SavingsAccountData postInterest(final MathContext mc, final LocalDate int savingsAccountData.updateTransactions(newPostingTransaction); if (savingsAccountData.getSavingsProductData().isAccrualBasedAccountingEnabled()) { - savingsAccountData.updateTransactions(SavingsAccountTransactionData.accrual(savingsAccountData, - interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting())); + savingsAccountData.updateTransactions( + SavingsAccountTransactionData.accrual(savingsAccountData, interestPostingTransactionDate, + interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting())); } if (applyWithHoldTaxForOldTransaction) { createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate, @@ -248,7 +247,7 @@ public List calculateInterestUsing(final MathContext mc, final Lo Money periodStartingBalance; if (savingsAccountData.getStartInterestCalculationDate() != null && !savingsAccountData.getStartInterestCalculationDate().equals(savingsAccountData.getActivationLocalDate())) { - final SavingsAccountTransactionData transaction = retrieveLastTransactions(savingsAccountData); + final SavingsAccountTransactionData transaction = retrieveLastTransaction(savingsAccountData); if (transaction == null) { periodStartingBalance = Money.zero(savingsAccountData.getCurrency()); @@ -332,13 +331,8 @@ private List retrieveListOfTransactions(final Sav } protected LocalDate getLockedInUntilLocalDate(final SavingsAccountData savingsAccount) { - LocalDate lockedInUntilLocalDate = null; - if (savingsAccount.getLockedInUntilDate() != null) { - lockedInUntilLocalDate = savingsAccount.getActivationLocalDate(); - // lockedInUntilLocalDate = LocalDate.ofInstant(this.lockedInUntilDate.toInstant(), - // DateUtils.getDateTimeZoneOfTenant()); - } - return lockedInUntilLocalDate; + LocalDate lockedInUntilLocalDate = savingsAccount.getLockedInUntilDate(); + return lockedInUntilLocalDate == null ? savingsAccount.getActivationLocalDate() : lockedInUntilLocalDate; } private BigDecimal minBalanceForInterestCalculation(final SavingsAccountData savingsAccountData) { @@ -361,20 +355,17 @@ public List getTransactions(final SavingsAccountD return savingsAccountData.getSavingsAccountTransactionData(); } - private SavingsAccountTransactionData retrieveLastTransactions(final SavingsAccountData savingsAccountData) { - if (savingsAccountData.getSavingsAccountTransactionData() != null - && savingsAccountData.getSavingsAccountTransactionData().size() == 1) { - return savingsAccountData.getSavingsAccountTransactionData().get(0); + private SavingsAccountTransactionData retrieveLastTransaction(@NotNull SavingsAccountData savingsAccountData) { + List transactions = savingsAccountData.getSavingsAccountTransactionData(); + if (transactions == null || transactions.isEmpty()) { + return savingsAccountData.getLastSavingsAccountTransaction(); // what is this? } - final List listOfTransactionsSorted = new ArrayList<>(); - listOfTransactionsSorted.addAll(savingsAccountData.getSavingsAccountTransactionData()); - if (!listOfTransactionsSorted.isEmpty()) { - final SavingsAccountTransactionDataComparator transactionComparator = new SavingsAccountTransactionDataComparator(); - Collections.sort(listOfTransactionsSorted, transactionComparator); - } else { - listOfTransactionsSorted.add(savingsAccountData.getLastSavingsAccountTransaction()); + if (transactions.size() == 1) { + return transactions.get(0); } - return listOfTransactionsSorted.get(0); + final List listOfTransactionsSorted = new ArrayList<>(transactions); + listOfTransactionsSorted.sort(new SavingsAccountTransactionDataComparator()); + return listOfTransactionsSorted.get(0); // this is the first transaction, not the last } public LocalDate getStartInterestCalculationDate(final SavingsAccountData savingsAccountData) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index e6679f6162f..a512ad8c5c7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -18,6 +18,9 @@ */ package org.apache.fineract.portfolio.savings.service; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD; + import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; @@ -278,7 +281,8 @@ public List retrieveAllSavingsDataForInterestPosting(final b } sql = sql + " and (sa.interest_posted_till_date is null or sa.interest_posted_till_date <= ? ) "; - sql = sql + " order by sa.id, tr.transaction_date, tr.created_date, tr.id"; + // #audit backward compatibility + sql = sql + " order by sa.id, tr.transaction_date, tr." + CREATED_DATE_DB_FIELD + ", tr.created_date, tr.id"; List savingsAccountDataList = this.jdbcTemplate.query(sql, this.savingAccountMapperForInterestPosting, // NOSONAR new Object[] { maxSavingsId, status, pageSize, yesterday }); @@ -459,19 +463,19 @@ public List extractData(final ResultSet rs) throws SQLExcept Integer daysToDormancy = null; Integer daysToEscheat = null; - LocalDate localTenantDate = DateUtils.getBusinessLocalDate(); + LocalDate currentDate = DateUtils.getBusinessLocalDate(); if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { - daysToEscheat = Math.toIntExact( - ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToEscheat))); + daysToEscheat = Math + .toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat))); } if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { daysToDormancy = Math.toIntExact( - ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToDormancy))); + ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy))); } if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { daysToInactive = Math.toIntExact( - ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToInactive))); + ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive))); } } final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); @@ -896,19 +900,19 @@ public SavingsAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") Integer daysToDormancy = null; Integer daysToEscheat = null; - LocalDate localTenantDate = DateUtils.getBusinessLocalDate(); + LocalDate currentDate = DateUtils.getBusinessLocalDate(); if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { daysToEscheat = Math - .toIntExact(ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToEscheat))); + .toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat))); } if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { daysToDormancy = Math - .toIntExact(ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToDormancy))); + .toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy))); } if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { daysToInactive = Math - .toIntExact(ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToInactive))); + .toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive))); } } @@ -1266,10 +1270,10 @@ public SavingsAccountTransactionData retrieveDepositTransactionTemplate(final Lo @Override public Collection retrieveAllTransactions(final Long savingsId, DepositAccountType depositAccountType) { - + // #audit backward compatibility final String sql = "select " + this.transactionsMapper.schema() - + " where sa.id = ? and sa.deposit_type_enum = ? order by tr.transaction_date DESC, tr.created_date DESC, tr.id DESC"; - + + " where sa.id = ? and sa.deposit_type_enum = ? order by tr.transaction_date DESC," + " tr." + CREATED_DATE_DB_FIELD + + " DESC, tr.created_date DESC, tr.id DESC"; return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue() }); // NOSONAR } @@ -1352,8 +1356,8 @@ private static String buildFrom() { + "left join m_account_transfer_transaction fromtran on fromtran.from_savings_transaction_id = tr.id " + "left join m_account_transfer_transaction totran on totran.to_savings_transaction_id = tr.id " + "left join m_payment_detail pd on tr.payment_detail_id = pd.id " - + "left join m_payment_type pt on pd.payment_type_id = pt.id left join m_appuser au on au.id=tr.appuser_id " - + "left join m_note nt ON nt.savings_account_transaction_id=tr.id "; + + "left join m_payment_type pt on pd.payment_type_id = pt.id left join m_appuser au on au.id= tr." + CREATED_BY_DB_FIELD + + " left join m_note nt ON nt.savings_account_transaction_id=tr.id "; } public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index c0824f4d86d..6affdff7a1f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -50,6 +50,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.batch.exception.ErrorHandler; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; @@ -166,6 +167,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final GSIMRepositoy gsimRepository; private final SavingsAccountInterestPostingService savingsAccountInterestPostingService; private final SavingsAccrualWritePlatformService savingsAccrualWritePlatformService; + private final ErrorHandler errorHandler; @Transactional @Override @@ -206,7 +208,7 @@ public CommandProcessingResult activate(final Long savingsId, final JsonCommand updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - final Map changes = account.activate(user, command, DateUtils.getBusinessLocalDate()); + final Map changes = account.activate(user, command); entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVINGS.getName(), StatusEnum.ACTIVATE.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(), account.productId()); @@ -234,8 +236,7 @@ public CommandProcessingResult activate(final Long savingsId, final JsonCommand @Override public void processPostActiveActions(final SavingsAccount account, final DateTimeFormatter fmt, final Set existingTransactionIds, final Set existingReversedTransactionIds) { - - AppUser user = getAppUserIfPresent(); + getAppUserIfPresent(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -245,13 +246,13 @@ public void processPostActiveActions(final SavingsAccount account, final DateTim boolean isRegularTransaction = false; if (amountForDeposit.isGreaterThanZero()) { boolean isAccountTransfer = false; - this.savingsAccountDomainService.handleDeposit(account, fmt, account.getActivationLocalDate(), amountForDeposit.getAmount(), - null, isAccountTransfer, isRegularTransaction, false); + this.savingsAccountDomainService.handleDeposit(account, fmt, account.getActivationDate(), amountForDeposit.getAmount(), null, + isAccountTransfer, isRegularTransaction, false); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); } - account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user); + account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository @@ -265,7 +266,6 @@ public void processPostActiveActions(final SavingsAccount account, final DateTim @Transactional @Override public CommandProcessingResult gsimDeposit(final Long gsimId, final JsonCommand command) { - Long parentSavingId = gsimId; // GroupSavingsIndividualMonitoringparentSavings=gsimRepository.findById(parentSavingId).get(); List childSavings = this.savingAccountRepositoryWrapper.findByGsimId(gsimId); @@ -277,7 +277,6 @@ public CommandProcessingResult gsimDeposit(final Long gsimId, final JsonCommand CommandProcessingResult result = null; for (JsonElement element : savingsArray) { - result = deposit(element.getAsJsonObject().get("childAccountId").getAsLong(), JsonCommand.fromExistingCommand(command, element)); } @@ -287,7 +286,6 @@ public CommandProcessingResult gsimDeposit(final Long gsimId, final JsonCommand @Transactional @Override public CommandProcessingResult deposit(final Long savingsId, final JsonCommand command) { - this.context.authenticatedUser(); this.savingsAccountTransactionDataValidator.validate(command); @@ -348,7 +346,6 @@ public CommandProcessingResult deposit(final Long savingsId, final JsonCommand c .withSavingsId(savingsId) // .with(changes) // .build(); - } private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) { @@ -421,17 +418,16 @@ public CommandProcessingResult withdrawal(final Long savingsId, final JsonComman @Transactional @Override public CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, final Long accountId) { - - AppUser user = getAppUserIfPresent(); + getAppUserIfPresent(); final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository .findOneWithNotFoundDetection(savingsAccountChargeId, accountId); - final LocalDate todaysDate = DateUtils.getBusinessLocalDate(); + final LocalDate currentDate = DateUtils.getBusinessLocalDate(); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MM yyyy").withZone(DateUtils.getDateTimeZoneOfTenant()); - while (todaysDate.isAfter(savingsAccountCharge.getDueLocalDate())) { - this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt, user, false); + while (DateUtils.isBefore(savingsAccountCharge.getDueDate(), currentDate)) { + this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueDate(), savingsAccountCharge.amount(), fmt, false); } return new CommandProcessingResultBuilder() // @@ -446,7 +442,6 @@ public CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, @Transactional @Override public CommandProcessingResult calculateInterest(final Long savingsId) { - final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -481,7 +476,6 @@ public CommandProcessingResult calculateInterest(final Long savingsId) { @Override @Transactional public CommandProcessingResult postInterest(final JsonCommand command) { - Long savingsId = command.getSavingsId(); final boolean postInterestAs = command.booleanPrimitiveValueOfParameterNamed("isPostInterestAsOn"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); @@ -492,13 +486,11 @@ public CommandProcessingResult postInterest(final JsonCommand command) { this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account); - if (postInterestAs == true) { - + if (postInterestAs) { if (transactionDate == null) { - throw new PostInterestAsOnDateException(PostInterestAsOnExceptionType.VALID_DATE); } - if (transactionDate.isBefore(account.accountSubmittedOrActivationDate())) { + if (DateUtils.isBefore(transactionDate, account.accountSubmittedOrActivationDate())) { throw new PostInterestAsOnDateException(PostInterestAsOnExceptionType.ACTIVATION_DATE); } @@ -510,16 +502,14 @@ public CommandProcessingResult postInterest(final JsonCommand command) { } for (SavingsAccountTransaction savingTransaction : savingTransactions) { - if (transactionDate.isBefore(savingTransaction.getDateOf())) { + if (DateUtils.isBefore(transactionDate, savingTransaction.getDateOf())) { throw new PostInterestAsOnDateException(PostInterestAsOnExceptionType.LAST_TRANSACTION_DATE); } } - LocalDate today = DateUtils.getBusinessLocalDate(); - if (transactionDate.isAfter(today)) { + if (DateUtils.isDateInTheFuture(transactionDate)) { throw new PostInterestAsOnDateException(PostInterestAsOnExceptionType.FUTURE_DATE); } - } postInterest(account, postInterestAs, transactionDate, backdatedTxnsAllowedTill); @@ -537,7 +527,6 @@ public CommandProcessingResult postInterest(final JsonCommand command) { @Override public void postInterest(final SavingsAccount account, final boolean postInterestAs, final LocalDate transactionDate, final boolean backdatedTxnsAllowedTill) { - final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -561,8 +550,8 @@ public void postInterest(final SavingsAccount account, final boolean postInteres postInterestOnDate = transactionDate; } boolean postReversals = false; - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); if (!backdatedTxnsAllowedTill) { List transactions = account.getTransactions(); @@ -693,7 +682,7 @@ public CommandProcessingResult undoTransaction(final Long savingsId, final Long throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); } - this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(savingsAccountTransaction.getTransactionLocalDate(), + this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(savingsAccountTransaction.getTransactionDate(), account); if (!allowAccountTransferModification @@ -730,9 +719,9 @@ public CommandProcessingResult undoTransaction(final Long savingsId, final Long boolean postReversals = false; checkClientOrGroupActive(account); if (savingsAccountTransaction.isPostInterestCalculationRequired() - && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + && account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), false)) { + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, postReversals); @@ -758,8 +747,7 @@ public CommandProcessingResult undoTransaction(final Long savingsId, final Long @Override public CommandProcessingResult adjustSavingsTransaction(final Long savingsId, final Long transactionId, final JsonCommand command) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -821,7 +809,7 @@ public CommandProcessingResult adjustSavingsTransaction(final Long savingsId, fi boolean isInterestTransfer = false; Integer accountType = null; final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, savingsAccountTransaction.getCreatedDate(), user, accountType); + paymentDetail, null, accountType); final String refNo = ExternalId.generate().getValue(); if (savingsAccountTransaction.isDeposit()) { transaction = account.deposit(transactionDTO, false, relaxingDaysConfigForPivotDate, refNo); @@ -832,9 +820,9 @@ public CommandProcessingResult adjustSavingsTransaction(final Long savingsId, fi final LocalDate postInterestOnDate = null; boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, false) - || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false, postReversals); + || account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), false)) { + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, postReversals); @@ -940,12 +928,12 @@ public CommandProcessingResult close(final Long savingsId, final JsonCommand com List savingTransactions = account.getTransactions(); for (SavingsAccountTransaction savingTransaction : savingTransactions) { if (savingTransaction.isInterestPosting() && savingTransaction.isNotReversed() - && closedDate.isEqual(savingTransaction.getTransactionLocalDate())) { + && DateUtils.isEqual(closedDate, savingTransaction.getTransactionDate())) { postInterestOnClosingDate = true; break; } } - if (postInterestOnClosingDate == false) { + if (!postInterestOnClosingDate) { throw new PostInterestClosingDateException(); } } @@ -970,7 +958,7 @@ public CommandProcessingResult close(final Long savingsId, final JsonCommand com } - final Map accountChanges = account.close(user, command, DateUtils.getBusinessLocalDate()); + final Map accountChanges = account.close(user, command); changes.putAll(accountChanges); if (!changes.isEmpty()) { this.savingAccountRepositoryWrapper.save(account); @@ -998,8 +986,7 @@ public CommandProcessingResult close(final Long savingsId, final JsonCommand com @Override public SavingsAccountTransaction initiateSavingsTransfer(final SavingsAccount savingsAccount, final LocalDate transferDate) { - - AppUser user = getAppUserIfPresent(); + getAppUserIfPresent(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1013,7 +1000,7 @@ public SavingsAccountTransaction initiateSavingsTransfer(final SavingsAccount sa updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction newTransferTransaction = SavingsAccountTransaction.initiateTransfer(savingsAccount, - savingsAccount.office(), transferDate, user); + savingsAccount.office(), transferDate); savingsAccount.addTransaction(newTransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue()); final MathContext mc = MathContext.DECIMAL64; @@ -1033,8 +1020,7 @@ public SavingsAccountTransaction initiateSavingsTransfer(final SavingsAccount sa @Override public SavingsAccountTransaction withdrawSavingsTransfer(final SavingsAccount savingsAccount, final LocalDate transferDate) { - - AppUser user = getAppUserIfPresent(); + getAppUserIfPresent(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1045,7 +1031,7 @@ public SavingsAccountTransaction withdrawSavingsTransfer(final SavingsAccount sa updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction withdrawtransferTransaction = SavingsAccountTransaction.withdrawTransfer(savingsAccount, - savingsAccount.office(), transferDate, user); + savingsAccount.office(), transferDate); savingsAccount.addTransaction(withdrawtransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); final MathContext mc = MathContext.DECIMAL64; @@ -1073,8 +1059,7 @@ public void rejectSavingsTransfer(final SavingsAccount savingsAccount) { @Override public SavingsAccountTransaction acceptSavingsTransfer(final SavingsAccount savingsAccount, final LocalDate transferDate, final Office acceptedInOffice, final Staff fieldOfficer) { - - AppUser user = getAppUserIfPresent(); + getAppUserIfPresent(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1085,7 +1070,7 @@ public SavingsAccountTransaction acceptSavingsTransfer(final SavingsAccount savi updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction acceptTransferTransaction = SavingsAccountTransaction.approveTransfer(savingsAccount, - acceptedInOffice, transferDate, user); + acceptedInOffice, transferDate); savingsAccount.addTransaction(acceptTransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); if (fieldOfficer != null) { @@ -1109,7 +1094,6 @@ public SavingsAccountTransaction acceptSavingsTransfer(final SavingsAccount savi @Transactional @Override public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command) { - this.context.authenticatedUser(); final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -1143,17 +1127,17 @@ public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command savingsAccountCharge.setFreeWithdrawalCount(0); } - if (savingsAccountCharge.getDueLocalDate() != null) { + if (savingsAccountCharge.getDueDate() != null) { // transaction date should not be on a holiday or non working day if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled() - && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().format(fmt)) + && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueDate().format(fmt)) .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); } if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled() - && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().format(fmt)) + && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueDate().format(fmt)) .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); } } @@ -1165,10 +1149,10 @@ public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command this.savingsAccountChargeRepository.save(savingsAccountCharge); if (savingsAccount.isAccrualBasedAccountingEnabledOnSavingsProduct()) { - if (savingsAccrualWritePlatformService.isChargeToBeRecognizedAsAccrual( - savingsAccount.savingsProduct().accrualChargeIds(), savingsAccountCharge)) { + if (savingsAccrualWritePlatformService.isChargeToBeRecognizedAsAccrual(savingsAccount.savingsProduct().accrualChargeIds(), + savingsAccountCharge)) { final SavingsAccountTransaction savingsAccountAccrualTransaction = savingsAccrualWritePlatformService - .addSavingsChargeAccrualTransaction(savingsAccount, savingsAccountCharge, savingsAccountCharge.getDueLocalDate()); + .addSavingsChargeAccrualTransaction(savingsAccount, savingsAccountCharge, savingsAccountCharge.getDueDate()); savingsAccountTransactionRepository.save(savingsAccountAccrualTransaction); } } @@ -1185,7 +1169,7 @@ public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command } postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds, backdatedTxnsAllowedTill); - + return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountCharge.getId()) // .withOfficeId(savingsAccount.officeId()) // @@ -1217,14 +1201,14 @@ public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand comm final Map changes = savingsAccountCharge.update(command); - if (savingsAccountCharge.getDueLocalDate() != null) { + if (savingsAccountCharge.getDueDate() != null) { final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); // transaction date should not be on a holiday or non working day if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled() - && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().format(fmt)) + && this.holidayRepository.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueDate().format(fmt)) .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); @@ -1232,8 +1216,8 @@ public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand comm } if (!this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled() - && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().format(fmt)) + && !this.workingDaysRepository.isWorkingDay(savingsAccountCharge.getDueDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueDate().format(fmt)) .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); @@ -1256,8 +1240,7 @@ public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand comm @Transactional @Override public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1282,16 +1265,16 @@ public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Lo updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); } - account.waiveCharge(savingsAccountChargeId, user, backdatedTxnsAllowedTill); + account.waiveCharge(savingsAccountChargeId, backdatedTxnsAllowedTill); boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; boolean postReversals = false; - if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), backdatedTxnsAllowedTill)) { + if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueDate(), backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -1348,8 +1331,7 @@ public CommandProcessingResult deleteSavingsAccountCharge(final Long savingsAcco @Override public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) { - - AppUser user = getAppUserIfPresent(); + context.authenticatedUser(); this.savingsAccountChargeDataValidator.validatePayCharge(command.json()); final Locale locale = command.extractLocale(); @@ -1385,7 +1367,7 @@ public CommandProcessingResult payCharge(final Long savingsAccountId, final Long final boolean backdatedTxnsAllowedTill = false; - SavingsAccountTransaction chargeTransaction = this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt, user, + SavingsAccountTransaction chargeTransaction = this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt, backdatedTxnsAllowedTill); final String noteText = command.stringValueOfParameterNamed("note"); @@ -1408,16 +1390,14 @@ public CommandProcessingResult payCharge(final Long savingsAccountId, final Long @Override public void applyChargeDue(final Long savingsAccountChargeId, final Long accountId) { // always use current date as transaction date for batch job - AppUser user = null; - final LocalDate transactionDate = DateUtils.getBusinessLocalDate(); final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository .findOneWithNotFoundDetection(savingsAccountChargeId, accountId); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MM yyyy").withZone(DateUtils.getDateTimeZoneOfTenant()); - while (transactionDate.isAfter(savingsAccountCharge.getDueLocalDate()) && savingsAccountCharge.isNotFullyPaid()) { - payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt, user, false); + while (savingsAccountCharge.isNotFullyPaid() && DateUtils.isBefore(savingsAccountCharge.getDueDate(), transactionDate)) { + payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt, false); } } @@ -1425,19 +1405,13 @@ public void applyChargeDue(final Long savingsAccountChargeId, final Long account private SavingsAccountData fallbackPostInterest(SavingsAccountData savingsAccountData, boolean postInterestAs, LocalDate transactionDate, boolean backdatedTxnsAllowedTill, Throwable t) { // NOTE: allow caller to catch the exceptions - - if (t instanceof RuntimeException re) { - throw re; - } - // NOTE: wrap throwable only if really necessary - throw new RuntimeException(t); + throw errorHandler.getMappable(t, null, null, "savings.postinterest"); } @Transactional private SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, - final BigDecimal amountPaid, final DateTimeFormatter formatter, final AppUser user, final boolean backdatedTxnsAllowedTill) { - + final BigDecimal amountPaid, final DateTimeFormatter formatter, final boolean backdatedTxnsAllowedTill) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -1455,7 +1429,7 @@ private SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAc account.validateAccountBalanceDoesNotViolateOverdraft(savingsAccountTransaction, amountPaid); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - SavingsAccountTransaction chargeTransaction = account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user, + SavingsAccountTransaction chargeTransaction = account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, backdatedTxnsAllowedTill, null); boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; @@ -1463,8 +1437,8 @@ private SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAc boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, isInterestTransfer, postReversals); + savingsAccountDomainService.postInterest(account, mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, isInterestTransfer, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -1485,10 +1459,10 @@ private SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAc this.savingAccountRepositoryWrapper.saveAndFlush(account); if (account.isAccrualBasedAccountingEnabledOnSavingsProduct()) { - if (savingsAccrualWritePlatformService.isChargeToBeRecognizedAsAccrual( - account.savingsProduct().accrualChargeIds(), savingsAccountCharge)) { + if (savingsAccrualWritePlatformService.isChargeToBeRecognizedAsAccrual(account.savingsProduct().accrualChargeIds(), + savingsAccountCharge)) { final SavingsAccountTransaction savingsAccountAccrualTransaction = savingsAccrualWritePlatformService - .addSavingsChargeAccrualTransaction(account, savingsAccountCharge, transactionDate); + .addSavingsChargeAccrualTransaction(account, savingsAccountCharge, transactionDate); savingsAccountTransactionRepository.save(savingsAccountAccrualTransaction); } } @@ -1704,7 +1678,7 @@ public void setSubStatusInactive(Long savingsId) { final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.setSubStatusInactive(appuserRepository.fetchSystemUser(), false); + account.setSubStatusInactive(false); this.savingAccountRepositoryWrapper.saveAndFlush(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false); } @@ -1822,16 +1796,15 @@ public CommandProcessingResult holdAmount(final Long savingsId, final JsonComman this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(), account, submittedBy, backdatedTxnsAllowedTill); - SavingsAccountTransaction transaction = this.savingsAccountDomainService.handleHold(account, getAppUserIfPresent(), amount, - transactionDate, lienAllowed); + SavingsAccountTransaction transaction = this.savingsAccountDomainService.handleHold(account, amount, transactionDate, lienAllowed); account.holdAmount(amount); - transaction.updateRunningBalance(runningBalance); + transaction.setRunningBalance(runningBalance); final String reasonForBlock = command.stringValueOfParameterNamed(SavingsApiConstants.reasonForBlockParamName); transaction.updateReason(reasonForBlock); account.getAccountBalance(); - this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transaction.getTransactionLocalDate(), account); + this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transaction.getTransactionDate(), account); this.savingsAccountTransactionRepository.saveAndFlush(transaction); @@ -1849,15 +1822,14 @@ public CommandProcessingResult holdAmount(final Long savingsId, final JsonComman @Transactional @Override public CommandProcessingResult releaseAmount(final Long savingsId, final Long savingsTransactionId) { - - final AppUser submittedBy = this.context.authenticatedUser(); + context.authenticatedUser(); SavingsAccountTransaction holdTransaction = this.savingsAccountTransactionRepository .findOneByIdAndSavingsAccountId(savingsTransactionId, savingsId); holdTransaction.updateReason(null); final SavingsAccountTransaction transaction = this.savingsAccountTransactionDataValidator - .validateReleaseAmountAndAssembleForm(holdTransaction, submittedBy); + .validateReleaseAmountAndAssembleForm(holdTransaction); final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); @@ -1870,9 +1842,9 @@ public CommandProcessingResult releaseAmount(final Long savingsId, final Long sa runningBalance = runningBalance.minus(savingsOnHold); runningBalance = runningBalance.plus(transaction.getAmount()); - transaction.updateRunningBalance(runningBalance); + transaction.setRunningBalance(runningBalance); - this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transaction.getTransactionLocalDate(), account); + this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transaction.getTransactionDate(), account); account.releaseOnHoldAmount(transaction.getAmount()); this.savingsAccountTransactionRepository.saveAndFlush(transaction); @@ -1880,6 +1852,8 @@ public CommandProcessingResult releaseAmount(final Long savingsId, final Long sa if (backdatedTxnsAllowedTill) { this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig()); + } else { + account.addTransaction(transaction); } this.savingAccountRepositoryWrapper.save(account); @@ -1967,16 +1941,14 @@ public CommandProcessingResult unblockDebits(final Long savingsId) { } private void validateTransactionsForTransfer(final SavingsAccount savingsAccount, final LocalDate transferDate) { - for (SavingsAccountTransaction transaction : savingsAccount.getTransactions()) { - if ((transaction.getTransactionLocalDate().isEqual(transferDate) && transaction.getTransactionLocalDate().isAfter(transferDate)) - || transaction.getTransactionLocalDate().isAfter(transferDate)) { + if ((DateUtils.isEqual(transferDate, transaction.getTransactionDate()) + && DateUtils.isEqual(transferDate, transaction.getSubmittedOnDate())) + || DateUtils.isBefore(transferDate, transaction.getTransactionDate())) { throw new GeneralPlatformDomainRuleException(TransferApiConstants.transferClientSavingsException, - TransferApiConstants.transferClientSavingsException, transaction.getCreatedDate(), transferDate); + TransferApiConstants.transferClientSavingsException, transaction.getCreatedDateTime(), transferDate); } - } - } private void validateReasonForHold(String reasonForBlock) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformService.java index 1df6e88ffca..02042ab77ab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformService.java @@ -20,17 +20,17 @@ import java.time.LocalDate; import java.util.Collection; - import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; public interface SavingsAccrualWritePlatformService { - + void addAccrualAccounting(Long savingsId); boolean isChargeToBeRecognizedAsAccrual(Collection chargeIds, SavingsAccountCharge savingsAccountCharge); - SavingsAccountTransaction addSavingsChargeAccrualTransaction(SavingsAccount savingsAccount, SavingsAccountCharge savingsAccountCharge, LocalDate transactionDate); + SavingsAccountTransaction addSavingsChargeAccrualTransaction(SavingsAccount savingsAccount, SavingsAccountCharge savingsAccountCharge, + LocalDate transactionDate); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java index 8bf2382e521..cef52ec872f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java @@ -20,7 +20,6 @@ import java.time.LocalDate; import java.util.Collection; - import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; @@ -32,7 +31,7 @@ @Service public class SavingsAccrualWritePlatformServiceImpl implements SavingsAccrualWritePlatformService { - + @Transactional @Override public void addAccrualAccounting(Long savingsId) { @@ -49,14 +48,14 @@ public boolean isChargeToBeRecognizedAsAccrual(final Collection chargeIds, @Transactional @Override - public SavingsAccountTransaction addSavingsChargeAccrualTransaction(SavingsAccount savingsAccount, - SavingsAccountCharge savingsAccountCharge, LocalDate transactionDate) { + public SavingsAccountTransaction addSavingsChargeAccrualTransaction(SavingsAccount savingsAccount, + SavingsAccountCharge savingsAccountCharge, LocalDate transactionDate) { final MonetaryCurrency currency = savingsAccount.getCurrency(); final Money chargeAmount = savingsAccountCharge.getAmount(currency); - SavingsAccountTransaction savingsAccountTransaction = SavingsAccountTransaction.accrual(savingsAccount, - savingsAccount.office(), transactionDate, chargeAmount, false); - final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(savingsAccountTransaction, - savingsAccountCharge, savingsAccountTransaction.getAmount(currency).getAmount()); + SavingsAccountTransaction savingsAccountTransaction = SavingsAccountTransaction.accrual(savingsAccount, savingsAccount.office(), + transactionDate, chargeAmount, false); + final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(savingsAccountTransaction, savingsAccountCharge, + savingsAccountTransaction.getAmount(currency).getAmount()); savingsAccountTransaction.getSavingsAccountChargesPaid().add(chargePaidBy); savingsAccount.addTransaction(savingsAccountTransaction); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index eea41b9429f..89d4d07e884 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -47,7 +47,6 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.dataqueries.data.EntityTables; import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService; @@ -320,7 +319,7 @@ public CommandProcessingResult modifyApplication(final Long savingsId, final Jso final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); checkClientOrGroupActive(account); account.modifyApplication(command, changes); - account.validateNewApplicationState(DateUtils.getBusinessLocalDate(), SAVINGS_ACCOUNT_RESOURCE_NAME); + account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); account.validateAccountValuesWithProduct(); if (!changes.isEmpty()) { @@ -397,7 +396,7 @@ public CommandProcessingResult modifyApplication(final Long savingsId, final Jso .build(); } catch (final DataAccessException dve) { handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); - return new CommandProcessingResult(Long.valueOf(-1)); + return CommandProcessingResult.resourceResult(-1L); } catch (final PersistenceException dve) { Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); handleDataIntegrityIssues(command, throwable, dve); @@ -480,7 +479,7 @@ public CommandProcessingResult approveApplication(final Long savingsId, final Js StatusEnum.APPROVE.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(), savingsAccount.productId()); - final Map changes = savingsAccount.approveApplication(currentUser, command, DateUtils.getBusinessLocalDate()); + final Map changes = savingsAccount.approveApplication(currentUser, command); if (!changes.isEmpty()) { this.savingAccountRepository.save(savingsAccount); @@ -603,7 +602,7 @@ public CommandProcessingResult rejectApplication(final Long savingsId, final Jso StatusEnum.REJECTED.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(), savingsAccount.productId()); - final Map changes = savingsAccount.rejectApplication(currentUser, command, DateUtils.getBusinessLocalDate()); + final Map changes = savingsAccount.rejectApplication(currentUser, command); if (!changes.isEmpty()) { this.savingAccountRepository.save(savingsAccount); @@ -640,8 +639,7 @@ public CommandProcessingResult applicantWithdrawsFromApplication(final Long savi StatusEnum.WITHDRAWN.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(), savingsAccount.productId()); - final Map changes = savingsAccount.applicantWithdrawsFromApplication(currentUser, command, - DateUtils.getBusinessLocalDate()); + final Map changes = savingsAccount.applicantWithdrawsFromApplication(currentUser, command); if (!changes.isEmpty()) { this.savingAccountRepository.save(savingsAccount); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java index ccf0d5f34b9..a3eda8f4774 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java @@ -18,12 +18,16 @@ */ package org.apache.fineract.portfolio.savings.service; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_BY_DB_FIELD; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_DATE_DB_FIELD; + import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.UUID; @@ -64,7 +68,6 @@ public class SavingsSchedularInterestPoster { @Transactional(isolation = Isolation.READ_UNCOMMITTED, rollbackFor = Exception.class) public void postInterest() throws JobExecutionException { - if (!savingAccounts.isEmpty()) { List errors = new ArrayList<>(); for (SavingsAccountData savingsAccountData : savingAccounts) { @@ -114,14 +117,14 @@ private void batchUpdateJournalEntries(final List savingsAcc savingsAccountTransactionData.setId(dataFromFetch.getId()); if (savingsAccountData.getGlAccountIdForSavingsControl() != 0 && savingsAccountData.getGlAccountIdForInterestOnSavings() != 0) { + OffsetDateTime auditDatetime = DateUtils.getAuditOffsetDateTime(); paramsForGLInsertion.add(new Object[] { savingsAccountData.getGlAccountIdForSavingsControl(), savingsAccountData.getOfficeId(), null, currencyCode, SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(), savingsAccountTransactionData.getId(), null, false, null, false, savingsAccountTransactionData.getTransactionDate(), JournalEntryType.CREDIT.getValue().longValue(), savingsAccountTransactionData.getAmount(), null, JournalEntryType.CREDIT.getValue().longValue(), - savingsAccountData.getId(), DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(), - DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(), false, BigDecimal.ZERO, BigDecimal.ZERO, null, + savingsAccountData.getId(), auditDatetime, auditDatetime, false, BigDecimal.ZERO, BigDecimal.ZERO, null, savingsAccountTransactionData.getTransactionDate(), null, userId, userId, DateUtils.getBusinessLocalDate() }); @@ -131,8 +134,7 @@ private void batchUpdateJournalEntries(final List savingsAcc savingsAccountTransactionData.getId(), null, false, null, false, savingsAccountTransactionData.getTransactionDate(), JournalEntryType.DEBIT.getValue().longValue(), savingsAccountTransactionData.getAmount(), null, JournalEntryType.DEBIT.getValue().longValue(), - savingsAccountData.getId(), DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(), - DateUtils.getOffsetDateTimeOfTenantWithMostPrecision(), false, BigDecimal.ZERO, BigDecimal.ZERO, null, + savingsAccountData.getId(), auditDatetime, auditDatetime, false, BigDecimal.ZERO, BigDecimal.ZERO, null, savingsAccountTransactionData.getTransactionDate(), null, userId, userId, DateUtils.getBusinessLocalDate() }); } @@ -147,16 +149,12 @@ private void batchUpdateJournalEntries(final List savingsAcc } private String batchQueryForJournalEntries() { - StringBuilder query = new StringBuilder(100); - - query.append("INSERT INTO acc_gl_journal_entry(account_id,office_id,reversal_id,currency_code,transaction_id,"); - query.append("savings_transaction_id,client_transaction_id,reversed,ref_num,manual_entry,entry_date,type_enum,"); - query.append("amount,description,entity_type_enum,entity_id,created_on_utc,"); - query.append("last_modified_on_utc,is_running_balance_calculated,office_running_balance,organization_running_balance,"); - query.append("payment_details_id,transaction_date,share_transaction_id, created_by, last_modified_by, submitted_on_date) "); - query.append("VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - - return query.toString(); + return "INSERT INTO acc_gl_journal_entry(account_id,office_id,reversal_id,currency_code,transaction_id," + + "savings_transaction_id,client_transaction_id,reversed,ref_num,manual_entry,entry_date,type_enum," + + "amount,description,entity_type_enum,entity_id,created_on_utc," + + "last_modified_on_utc,is_running_balance_calculated,office_running_balance,organization_running_balance," + + "payment_details_id,transaction_date,share_transaction_id, created_by, last_modified_by, submitted_on_date) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; } private List fetchTransactionsFromIds(final List refNo) throws DataAccessException { @@ -172,7 +170,10 @@ private void batchUpdate(final List savingsAccountDataList) List paramsForSavingsSummary = new ArrayList<>(); List paramsForTransactionUpdate = new ArrayList<>(); List transRefNo = new ArrayList<>(); + LocalDate currentDate = DateUtils.getBusinessLocalDate(); + Long userId = platformSecurityContext.authenticatedUser().getId(); for (SavingsAccountData savingsAccountData : savingsAccountDataList) { + OffsetDateTime auditTime = DateUtils.getAuditOffsetDateTime(); SavingsAccountSummaryData savingsAccountSummaryData = savingsAccountData.getSummary(); paramsForSavingsSummary.add(new Object[] { savingsAccountSummaryData.getTotalDeposits(), savingsAccountSummaryData.getTotalWithdrawals(), savingsAccountSummaryData.getTotalInterestEarned(), @@ -180,39 +181,30 @@ private void batchUpdate(final List savingsAccountDataList) savingsAccountSummaryData.getTotalFeeCharge(), savingsAccountSummaryData.getTotalPenaltyCharge(), savingsAccountSummaryData.getTotalAnnualFees(), savingsAccountSummaryData.getAccountBalance(), savingsAccountSummaryData.getTotalOverdraftInterestDerived(), savingsAccountSummaryData.getTotalWithholdTax(), - Date.from(savingsAccountSummaryData.getLastInterestCalculationDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant()) - .toInstant()), - savingsAccountSummaryData.getInterestPostedTillDate() != null - ? Date.from(savingsAccountSummaryData.getInterestPostedTillDate() - .atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()) - : Date.from(savingsAccountSummaryData.getLastInterestCalculationDate() - .atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()), - savingsAccountData.getId() }); + savingsAccountSummaryData.getLastInterestCalculationDate(), + savingsAccountSummaryData.getInterestPostedTillDate() != null ? savingsAccountSummaryData.getInterestPostedTillDate() + : savingsAccountSummaryData.getLastInterestCalculationDate(), + auditTime, userId, savingsAccountData.getId() }); List savingsAccountTransactionDataList = savingsAccountData.getSavingsAccountTransactionData(); - LocalDateTime currentDate = DateUtils.getLocalDateTimeOfTenant(); for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) { - Date balanceEndDate = null; - if (savingsAccountTransactionData.getBalanceEndDate() != null) { - balanceEndDate = Date.from(savingsAccountTransactionData.getBalanceEndDate() - .atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); - } if (savingsAccountTransactionData.getId() == null) { UUID uuid = UUID.randomUUID(); savingsAccountTransactionData.setRefNo(uuid.toString()); transRefNo.add(uuid.toString()); paramsForTransactionInsertion.add(new Object[] { savingsAccountData.getId(), savingsAccountData.getOfficeId(), savingsAccountTransactionData.isReversed(), savingsAccountTransactionData.getTransactionType().getId(), - savingsAccountTransactionData.getTransactionDate(), savingsAccountTransactionData.getAmount(), balanceEndDate, - savingsAccountTransactionData.getBalanceNumberOfDays(), savingsAccountTransactionData.getRunningBalance(), - savingsAccountTransactionData.getCumulativeBalance(), currentDate, 1, - savingsAccountTransactionData.isManualTransaction(), savingsAccountTransactionData.getRefNo(), - savingsAccountTransactionData.isReversalTransaction(), savingsAccountTransactionData.getOverdraftAmount(), - DateUtils.getBusinessLocalDate() }); + savingsAccountTransactionData.getTransactionDate(), savingsAccountTransactionData.getAmount(), + savingsAccountTransactionData.getBalanceEndDate(), savingsAccountTransactionData.getBalanceNumberOfDays(), + savingsAccountTransactionData.getRunningBalance(), savingsAccountTransactionData.getCumulativeBalance(), + auditTime, userId, auditTime, userId, savingsAccountTransactionData.isManualTransaction(), + savingsAccountTransactionData.getRefNo(), savingsAccountTransactionData.isReversalTransaction(), + savingsAccountTransactionData.getOverdraftAmount(), currentDate }); } else { paramsForTransactionUpdate.add(new Object[] { savingsAccountTransactionData.isReversed(), - savingsAccountTransactionData.getAmount(), savingsAccountTransactionData.getOverdraftAmount(), balanceEndDate, - savingsAccountTransactionData.getBalanceNumberOfDays(), savingsAccountTransactionData.getRunningBalance(), - savingsAccountTransactionData.getCumulativeBalance(), savingsAccountTransactionData.isReversalTransaction(), + savingsAccountTransactionData.getAmount(), savingsAccountTransactionData.getOverdraftAmount(), + savingsAccountTransactionData.getBalanceEndDate(), savingsAccountTransactionData.getBalanceNumberOfDays(), + savingsAccountTransactionData.getRunningBalance(), savingsAccountTransactionData.getCumulativeBalance(), + savingsAccountTransactionData.isReversalTransaction(), auditTime, userId, savingsAccountTransactionData.getId() }); } } @@ -240,35 +232,23 @@ private void batchUpdate(final List savingsAccountDataList) } private String batchQueryForTransactionInsertion() { - StringBuilder query = new StringBuilder(100); - query.append("INSERT INTO m_savings_account_transaction (savings_account_id, office_id, is_reversed, "); - query.append("transaction_type_enum, transaction_date, amount, balance_end_date_derived, "); - query.append("balance_number_of_days_derived, running_balance_derived, cumulative_balance_derived, "); - query.append("created_date, appuser_id, is_manual, ref_no, is_reversal, "); - query.append("overdraft_amount_derived, submitted_on_date) VALUES "); - query.append("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - return query.toString(); - + return "INSERT INTO m_savings_account_transaction (savings_account_id, office_id, is_reversed, transaction_type_enum, transaction_date, amount, balance_end_date_derived, " + + "balance_number_of_days_derived, running_balance_derived, cumulative_balance_derived, " + CREATED_DATE_DB_FIELD + ", " + + CREATED_BY_DB_FIELD + ", " + LAST_MODIFIED_DATE_DB_FIELD + ", " + LAST_MODIFIED_BY_DB_FIELD + + ", is_manual, ref_no, is_reversal, " + + "overdraft_amount_derived, submitted_on_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; } private String batchQueryForSavingsSummaryUpdate() { - StringBuilder query = new StringBuilder(100); - query.append("update m_savings_account set total_deposits_derived=?, total_withdrawals_derived=?, "); - query.append("total_interest_earned_derived=?, total_interest_posted_derived=?, total_withdrawal_fees_derived=?, "); - query.append("total_fees_charge_derived=?, total_penalty_charge_derived=?, total_annual_fees_derived=?, "); - query.append("account_balance_derived=?, total_overdraft_interest_derived=?, total_withhold_tax_derived=?, "); - query.append("last_interest_calculation_date=?, interest_posted_till_date=? where id=? "); - return query.toString(); + return "update m_savings_account set total_deposits_derived=?, total_withdrawals_derived=?, total_interest_earned_derived=?, total_interest_posted_derived=?, total_withdrawal_fees_derived=?, " + + "total_fees_charge_derived=?, total_penalty_charge_derived=?, total_annual_fees_derived=?, account_balance_derived=?, total_overdraft_interest_derived=?, total_withhold_tax_derived=?, " + + "last_interest_calculation_date=?, interest_posted_till_date=?, " + LAST_MODIFIED_DATE_DB_FIELD + " = ?, " + + LAST_MODIFIED_BY_DB_FIELD + " = ? WHERE id=? "; } private String batchQueryForTransactionsUpdate() { - StringBuilder query = new StringBuilder(100); - query.append("UPDATE m_savings_account_transaction "); - query.append("SET is_reversed=?, "); - query.append("amount=?, overdraft_amount_derived=?, balance_end_date_derived=?, "); - query.append("balance_number_of_days_derived=?, running_balance_derived=?, cumulative_balance_derived=?, "); - query.append("is_reversal=? "); - query.append("WHERE id=?"); - return query.toString(); + return "UPDATE m_savings_account_transaction " + + "SET is_reversed=?, amount=?, overdraft_amount_derived=?, balance_end_date_derived=?, balance_number_of_days_derived=?, running_balance_derived=?, cumulative_balance_derived=?, is_reversal=?, " + + LAST_MODIFIED_DATE_DB_FIELD + " = ?, " + LAST_MODIFIED_BY_DB_FIELD + " = ? " + "WHERE id=?"; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java index db724134d22..b307fd2fdd6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.savings.service.search; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import com.google.gson.JsonObject; @@ -87,7 +88,7 @@ public Page searchTransactions(@NotNull Long savi sortPageable = pageable.withSort(Sort.by(orders.stream() .map(e -> e.withProperty(SearchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList())); } else { - pageable = pageable.withSort(Sort.Direction.DESC, "transaction_date", "created_date", "id"); + pageable = pageable.withSort(Sort.Direction.DESC, "transaction_date", CREATED_DATE_DB_FIELD, "id"); sortPageable = pageable; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchConditions.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchConditions.java index 7176144fddd..633ebe85432 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchConditions.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/SearchConditions.java @@ -36,18 +36,18 @@ public SearchConditions(final String searchQueryParam, final String searchResour this.searchQuery = searchQueryParam; this.searchResource = searchResource; this.exactMatch = exactMatch; - this.clientSearch = (null == searchResource - || searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTS.name().toLowerCase())) ? true : false; - this.groupSearch = (null == searchResource - || searchResource.toLowerCase().contains(SearchSupportedResources.GROUPS.name().toLowerCase())) ? true : false; - this.loanSeach = (null == searchResource - || searchResource.toLowerCase().contains(SearchSupportedResources.LOANS.name().toLowerCase())) ? true : false; - this.savingSeach = (null == searchResource - || searchResource.toLowerCase().contains(SearchSupportedResources.SAVINGS.name().toLowerCase())) ? true : false; - this.shareSeach = (null == searchResource - || searchResource.toLowerCase().contains(SearchSupportedResources.SHARES.name().toLowerCase())) ? true : false; - this.clientIdentifierSearch = (null == searchResource - || searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTIDENTIFIERS.name().toLowerCase())) ? true : false; + this.clientSearch = null == searchResource + || searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTS.name().toLowerCase()); + this.groupSearch = null == searchResource + || searchResource.toLowerCase().contains(SearchSupportedResources.GROUPS.name().toLowerCase()); + this.loanSeach = null == searchResource + || searchResource.toLowerCase().contains(SearchSupportedResources.LOANS.name().toLowerCase()); + this.savingSeach = null == searchResource + || searchResource.toLowerCase().contains(SearchSupportedResources.SAVINGS.name().toLowerCase()); + this.shareSeach = null == searchResource + || searchResource.toLowerCase().contains(SearchSupportedResources.SHARES.name().toLowerCase()); + this.clientIdentifierSearch = null == searchResource + || searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTIDENTIFIERS.name().toLowerCase()); } public SearchConditions(final String searchQueryParam, final String searchResource, final Boolean clientSearch, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResourceSwagger.java index cdda461cf16..733745642d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResourceSwagger.java @@ -39,7 +39,7 @@ static final class GetAccountOptions { private GetAccountOptions() {} @Schema(example = "2") - public Integer id; + public Long id; @Schema(example = "accountType.savings") public String code; @Schema(example = "Savings Account") @@ -53,16 +53,16 @@ static final class GetFromAccountOptions { private GetFromAccountOptions() {} @Schema(example = "2") - public Integer accountId; + public Long accountId; @Schema(example = "00000001") public Integer accountNo; public GetAccountTransferTemplateResponse.GetAccountOptions accountType; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "ABC") public String clientName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "HEAD OFFICE") public String officeName; @@ -75,16 +75,16 @@ static final class GetToAccountOptions { private GetToAccountOptions() {} @Schema(example = "2") - public Integer accountId; + public Long accountId; @Schema(example = "00000001") public Integer accountNo; public GetAccountTransferTemplateResponse.GetAccountOptions accountType; @Schema(example = "1") - public Integer clientId; + public Long clientId; @Schema(example = "ABC") public String clientName; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "HEAD OFFICE") public String officeName; @@ -133,8 +133,8 @@ public static final class PostNewTransferResponse { private PostNewTransferResponse() {} @Schema(example = "1") - public Integer savingsId; + public Long savingsId; @Schema(example = "1") - public Integer resourceId; + public Long resourceId; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java index 0970f5f27bf..ea0fc832cdb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java @@ -21,23 +21,17 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData; import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class SelfAccountTransferReadServiceImpl implements SelfAccountTransferReadService { private final JdbcTemplate jdbcTemplate; - @Autowired - public SelfAccountTransferReadServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - @Override public Collection retrieveSelfAccountTemplateData(AppUser user) { SelfAccountTemplateMapper mapper = new SelfAccountTemplateMapper(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java index ff1a7c90664..4bb1aec727d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java @@ -28,12 +28,9 @@ import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData; import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTData; import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service public class SelfBeneficiariesTPTReadPlatformServiceImpl implements SelfBeneficiariesTPTReadPlatformService { private final PlatformSecurityContext context; @@ -41,7 +38,6 @@ public class SelfBeneficiariesTPTReadPlatformServiceImpl implements SelfBenefici private final BeneficiaryMapper mapper; private final AccountTemplateMapper accountTemplateMapper; - @Autowired public SelfBeneficiariesTPTReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { this.context = context; this.jdbcTemplate = jdbcTemplate; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java index b023b4239ec..6ec8c37ced0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java @@ -26,6 +26,8 @@ import java.util.HashMap; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; @@ -42,35 +44,19 @@ import org.apache.fineract.portfolio.self.account.exception.InvalidAccountInformationException; import org.apache.fineract.portfolio.self.account.exception.InvalidBeneficiaryException; import org.apache.fineract.useradministration.domain.AppUser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor +@Slf4j public class SelfBeneficiariesTPTWritePlatformServiceImpl implements SelfBeneficiariesTPTWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(SelfBeneficiariesTPTWritePlatformServiceImpl.class); private final PlatformSecurityContext context; private final SelfBeneficiariesTPTRepository repository; private final SelfBeneficiariesTPTDataValidator validator; private final LoanRepositoryWrapper loanRepositoryWrapper; private final SavingsAccountRepositoryWrapper savingRepositoryWrapper; - @Autowired - public SelfBeneficiariesTPTWritePlatformServiceImpl(final PlatformSecurityContext context, - final SelfBeneficiariesTPTRepository repository, final SelfBeneficiariesTPTDataValidator validator, - final LoanRepositoryWrapper loanRepositoryWrapper, final SavingsAccountRepositoryWrapper savingRepositoryWrapper) { - this.context = context; - this.repository = repository; - this.validator = validator; - this.loanRepositoryWrapper = loanRepositoryWrapper; - this.savingRepositoryWrapper = savingRepositoryWrapper; - - } - @Transactional @Override public CommandProcessingResult add(JsonCommand command) { @@ -178,7 +164,7 @@ private void handleDataIntegrityIssues(final JsonCommand command, final DataAcce "Beneficiary with name `" + name + "` already exists", NAME_PARAM_NAME, name); } - LOG.error("Error occured.", dae); + log.error("Error occured.", dae); throw new PlatformDataIntegrityException("error.msg.beneficiary.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/starter/SelfAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/starter/SelfAccountConfiguration.java new file mode 100644 index 00000000000..81189ec0983 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/starter/SelfAccountConfiguration.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.starter; + +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; +import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTDataValidator; +import org.apache.fineract.portfolio.self.account.domain.SelfBeneficiariesTPTRepository; +import org.apache.fineract.portfolio.self.account.service.SelfAccountTransferReadService; +import org.apache.fineract.portfolio.self.account.service.SelfAccountTransferReadServiceImpl; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTReadPlatformService; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTReadPlatformServiceImpl; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTWritePlatformService; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTWritePlatformServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class SelfAccountConfiguration { + + @Bean + @ConditionalOnMissingBean(SelfAccountTransferReadService.class) + public SelfAccountTransferReadService selfAccountTransferReadService(JdbcTemplate jdbcTemplate) { + return new SelfAccountTransferReadServiceImpl(jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(SelfBeneficiariesTPTReadPlatformService.class) + public SelfBeneficiariesTPTReadPlatformService selfBeneficiariesTPTReadPlatformService(PlatformSecurityContext context, + JdbcTemplate jdbcTemplate) { + return new SelfBeneficiariesTPTReadPlatformServiceImpl(context, jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(SelfBeneficiariesTPTWritePlatformService.class) + public SelfBeneficiariesTPTWritePlatformService selfBeneficiariesTPTWritePlatformService(PlatformSecurityContext context, + SelfBeneficiariesTPTRepository repository, SelfBeneficiariesTPTDataValidator validator, + LoanRepositoryWrapper loanRepositoryWrapper, SavingsAccountRepositoryWrapper savingRepositoryWrapper) { + return new SelfBeneficiariesTPTWritePlatformServiceImpl(context, repository, validator, loanRepositoryWrapper, + savingRepositoryWrapper); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadServiceImpl.java index 6f53e069752..d89168ace87 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/service/AppuserClientMapperReadServiceImpl.java @@ -18,25 +18,18 @@ */ package org.apache.fineract.portfolio.self.client.service; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.client.exception.ClientNotFoundException; import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class AppuserClientMapperReadServiceImpl implements AppuserClientMapperReadService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - @Autowired - public AppuserClientMapperReadServiceImpl(final JdbcTemplate jdbcTemplate, final PlatformSecurityContext context) { - this.jdbcTemplate = jdbcTemplate; - this.context = context; - } - @Override public Boolean isClientMappedToUser(Long clientId, Long appUserId) { return this.jdbcTemplate.queryForObject( diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/starter/SelfClientConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/starter/SelfClientConfiguration.java new file mode 100644 index 00000000000..358afa7aa19 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/client/starter/SelfClientConfiguration.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.client.starter; + +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.self.client.service.AppuserClientMapperReadService; +import org.apache.fineract.portfolio.self.client.service.AppuserClientMapperReadServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class SelfClientConfiguration { + + @Bean + @ConditionalOnMissingBean(AppuserClientMapperReadService.class) + public AppuserClientMapperReadService appuserClientMapperReadService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context) { + return new AppuserClientMapperReadServiceImpl(jdbcTemplate, context); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResourceSwagger.java index 4c6ca8b1a6b..c3639feba80 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResourceSwagger.java @@ -39,7 +39,7 @@ static final class GetPocketAccountDetail { private GetPocketAccountDetail() {} @Schema(example = "11") - public Integer accountId; + public Long accountId; @Schema(example = "LOAN") public String accountType; } @@ -66,15 +66,15 @@ static final class GetPocketLoanAccounts { private GetPocketLoanAccounts() {} @Schema(example = "6") - public Integer pocketId; + public Long pocketId; @Schema(example = "11") - public Integer accountId; + public Long accountId; @Schema(example = "2") public Integer accountType; @Schema(example = "000000011") public Integer accountNumber; @Schema(example = "10") - public Integer id; + public Long id; } static final class GetPocketSavingAccounts { @@ -82,15 +82,15 @@ static final class GetPocketSavingAccounts { private GetPocketSavingAccounts() {} @Schema(example = "6") - public Integer pocketId; + public Long pocketId; @Schema(example = "2") - public Integer accountId; + public Long accountId; @Schema(example = "3") public Integer accountType; @Schema(example = "000000002") public Integer accountNumber; @Schema(example = "11") - public Integer id; + public Long id; } static final class GetPocketShareAccounts { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceFactory.java index a3427e61b5a..21d665861b2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceFactory.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceFactory.java @@ -22,17 +22,11 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; -@Component -@Scope("singleton") public class AccountEntityServiceFactory { private Map accountEntityServiceHashMap = new HashMap<>(); - @Autowired public AccountEntityServiceFactory(final Set accountEntityServices) { for (AccountEntityService service : accountEntityServices) { this.accountEntityServiceHashMap.put(service.getKey(), service); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForLoanImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForLoanImpl.java index 120b830134f..b16d9b27d51 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForLoanImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForLoanImpl.java @@ -19,15 +19,14 @@ package org.apache.fineract.portfolio.self.pockets.service; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.self.loanaccount.service.AppuserLoansMapperReadService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class AccountEntityServiceForLoanImpl implements AccountEntityService { private static final String KEY = EntityAccountType.LOAN.name(); @@ -36,16 +35,6 @@ public class AccountEntityServiceForLoanImpl implements AccountEntityService { private final AppuserLoansMapperReadService appuserLoansMapperReadService; private final LoanReadPlatformService loanReadPlatformService; - @Autowired - public AccountEntityServiceForLoanImpl(final PlatformSecurityContext context, - final AppuserLoansMapperReadService appuserLoansMapperReadService, final LoanReadPlatformService loanReadPlatformService) { - - this.context = context; - this.appuserLoansMapperReadService = appuserLoansMapperReadService; - this.loanReadPlatformService = loanReadPlatformService; - - } - @Override public String getKey() { return KEY; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForSavingsImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForSavingsImpl.java index c0089b1e8b7..8d772ec1bd0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForSavingsImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForSavingsImpl.java @@ -19,15 +19,14 @@ package org.apache.fineract.portfolio.self.pockets.service; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; import org.apache.fineract.portfolio.self.savings.service.AppuserSavingsMapperReadService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class AccountEntityServiceForSavingsImpl implements AccountEntityService { private static final String KEY = EntityAccountType.SAVINGS.name(); @@ -36,17 +35,6 @@ public class AccountEntityServiceForSavingsImpl implements AccountEntityService private final AppuserSavingsMapperReadService appuserSavingsMapperReadService; private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; - @Autowired - public AccountEntityServiceForSavingsImpl(final PlatformSecurityContext context, - final AppuserSavingsMapperReadService appuserSavingsMapperReadService, - final SavingsAccountReadPlatformService savingsAccountReadPlatformService) { - - this.context = context; - this.appuserSavingsMapperReadService = appuserSavingsMapperReadService; - this.savingsAccountReadPlatformService = savingsAccountReadPlatformService; - - } - @Override public String getKey() { return KEY; @@ -54,10 +42,8 @@ public String getKey() { @Override public void validateSelfUserAccountMapping(Long accountId) { - - if (!this.appuserSavingsMapperReadService.isSavingsMappedToUser(accountId, this.context.getAuthenticatedUserIfPresent().getId())) { + if (!this.appuserSavingsMapperReadService.isSavingsMappedToUser(accountId, this.context.authenticatedUser().getId())) { throw new SavingsAccountNotFoundException(accountId); - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForShareAccountsImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForShareAccountsImpl.java index a1c8ea814d9..8fb3269518d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForShareAccountsImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForShareAccountsImpl.java @@ -19,15 +19,14 @@ package org.apache.fineract.portfolio.self.pockets.service; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.accounts.exceptions.ShareAccountNotFoundException; import org.apache.fineract.portfolio.self.shareaccounts.service.AppUserShareAccountsMapperReadPlatformService; import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class AccountEntityServiceForShareAccountsImpl implements AccountEntityService { private static final String KEY = EntityAccountType.SHARES.name(); @@ -36,15 +35,6 @@ public class AccountEntityServiceForShareAccountsImpl implements AccountEntitySe private final AppUserShareAccountsMapperReadPlatformService appUserShareAccountsMapperReadPlatformService; private final ShareAccountReadPlatformService shareAccountReadPlatformService; - @Autowired - public AccountEntityServiceForShareAccountsImpl(final PlatformSecurityContext context, - final AppUserShareAccountsMapperReadPlatformService appUserShareAccountsMapperReadPlatformService, - final ShareAccountReadPlatformService shareAccountReadPlatformService) { - this.context = context; - this.appUserShareAccountsMapperReadPlatformService = appUserShareAccountsMapperReadPlatformService; - this.shareAccountReadPlatformService = shareAccountReadPlatformService; - } - @Override public String getKey() { return KEY; @@ -53,9 +43,8 @@ public String getKey() { @Override public void validateSelfUserAccountMapping(Long accountId) { if (!this.appUserShareAccountsMapperReadPlatformService.isShareAccountsMappedToUser(accountId, - this.context.getAuthenticatedUserIfPresent().getId())) { + this.context.authenticatedUser().getId())) { throw new ShareAccountNotFoundException(accountId); - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformServiceImpl.java index 1c534767adc..3f4d29b2019 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformServiceImpl.java @@ -20,17 +20,16 @@ import java.util.ArrayList; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.self.pockets.data.PocketAccountMappingData; import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMapping; import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMappingRepositoryWrapper; import org.apache.fineract.portfolio.self.pockets.domain.PocketRepositoryWrapper; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class PocketAccountMappingReadPlatformServiceImpl implements PocketAccountMappingReadPlatformService { private final JdbcTemplate jdbcTemplate; @@ -38,16 +37,6 @@ public class PocketAccountMappingReadPlatformServiceImpl implements PocketAccoun private final PocketRepositoryWrapper pocketRepositoryWrapper; private final PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper; - @Autowired - public PocketAccountMappingReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final PlatformSecurityContext context, - final PocketRepositoryWrapper pocketRepositoryWrapper, - final PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper) { - this.jdbcTemplate = jdbcTemplate; - this.context = context; - this.pocketRepositoryWrapper = pocketRepositoryWrapper; - this.pocketAccountMappingRepositoryWrapper = pocketAccountMappingRepositoryWrapper; - } - @Override public PocketAccountMappingData retrieveAll() { final Long pocketId = this.pocketRepositoryWrapper.findByAppUserId(this.context.authenticatedUser().getId()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformServiceImpl.java index 34c30cde5ec..1a9646bcbec 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformServiceImpl.java @@ -24,6 +24,7 @@ import com.google.gson.JsonObject; import java.util.ArrayList; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -36,11 +37,9 @@ import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMapping; import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMappingRepositoryWrapper; import org.apache.fineract.portfolio.self.pockets.domain.PocketRepositoryWrapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class PocketWritePlatformServiceImpl implements PocketWritePlatformService { private final PlatformSecurityContext context; @@ -50,19 +49,6 @@ public class PocketWritePlatformServiceImpl implements PocketWritePlatformServic private final PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper; private final PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService; - @Autowired - public PocketWritePlatformServiceImpl(final PlatformSecurityContext context, PocketDataValidator pocketDataValidator, - final AccountEntityServiceFactory accountEntityServiceFactory, final PocketRepositoryWrapper pocketRepositoryWrapper, - final PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper, - final PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService) { - this.context = context; - this.pocketDataValidator = pocketDataValidator; - this.accountEntityServiceFactory = accountEntityServiceFactory; - this.pocketRepositoryWrapper = pocketRepositoryWrapper; - this.pocketAccountMappingRepositoryWrapper = pocketAccountMappingRepositoryWrapper; - this.pocketAccountMappingReadPlatformService = pocketAccountMappingReadPlatformService; - } - @Transactional @Override public CommandProcessingResult linkAccounts(JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/starter/SelfPocketsConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/starter/SelfPocketsConfiguration.java new file mode 100644 index 00000000000..8d8245c40b2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/starter/SelfPocketsConfiguration.java @@ -0,0 +1,102 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.pockets.starter; + +import java.util.Set; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; +import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; +import org.apache.fineract.portfolio.self.loanaccount.service.AppuserLoansMapperReadService; +import org.apache.fineract.portfolio.self.pockets.data.PocketDataValidator; +import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMappingRepositoryWrapper; +import org.apache.fineract.portfolio.self.pockets.domain.PocketRepositoryWrapper; +import org.apache.fineract.portfolio.self.pockets.service.AccountEntityService; +import org.apache.fineract.portfolio.self.pockets.service.AccountEntityServiceFactory; +import org.apache.fineract.portfolio.self.pockets.service.AccountEntityServiceForLoanImpl; +import org.apache.fineract.portfolio.self.pockets.service.AccountEntityServiceForSavingsImpl; +import org.apache.fineract.portfolio.self.pockets.service.AccountEntityServiceForShareAccountsImpl; +import org.apache.fineract.portfolio.self.pockets.service.PocketAccountMappingReadPlatformService; +import org.apache.fineract.portfolio.self.pockets.service.PocketAccountMappingReadPlatformServiceImpl; +import org.apache.fineract.portfolio.self.pockets.service.PocketWritePlatformService; +import org.apache.fineract.portfolio.self.pockets.service.PocketWritePlatformServiceImpl; +import org.apache.fineract.portfolio.self.savings.service.AppuserSavingsMapperReadService; +import org.apache.fineract.portfolio.self.shareaccounts.service.AppUserShareAccountsMapperReadPlatformService; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class SelfPocketsConfiguration { + + @Bean + @Scope("singleton") + @ConditionalOnMissingBean(AccountEntityServiceFactory.class) + public AccountEntityServiceFactory accountEntityServiceFactory(Set accountEntityServices) { + return new AccountEntityServiceFactory(accountEntityServices); + } + + @Bean + @ConditionalOnMissingBean(AccountEntityServiceForLoanImpl.class) + public AccountEntityService accountEntityServiceForLoanImpl(PlatformSecurityContext context, + AppuserLoansMapperReadService appuserLoansMapperReadService, LoanReadPlatformService loanReadPlatformService) { + + return new AccountEntityServiceForLoanImpl(context, appuserLoansMapperReadService, loanReadPlatformService); + + } + + @Bean + @ConditionalOnMissingBean(AccountEntityServiceForSavingsImpl.class) + public AccountEntityService accountEntityServiceForSavingsImpl(PlatformSecurityContext context, + AppuserSavingsMapperReadService appuserSavingsMapperReadService, + SavingsAccountReadPlatformService savingsAccountReadPlatformService) { + + return new AccountEntityServiceForSavingsImpl(context, appuserSavingsMapperReadService, savingsAccountReadPlatformService); + } + + @Bean + @ConditionalOnMissingBean(AccountEntityServiceForShareAccountsImpl.class) + public AccountEntityService accountEntityServiceForShareAccountsImpl(PlatformSecurityContext context, + AppUserShareAccountsMapperReadPlatformService appUserShareAccountsMapperReadPlatformService, + ShareAccountReadPlatformService shareAccountReadPlatformService) { + return new AccountEntityServiceForShareAccountsImpl(context, appUserShareAccountsMapperReadPlatformService, + shareAccountReadPlatformService); + } + + @Bean + @ConditionalOnMissingBean(PocketAccountMappingReadPlatformService.class) + public PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService(JdbcTemplate jdbcTemplate, + PlatformSecurityContext context, PocketRepositoryWrapper pocketRepositoryWrapper, + PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper) { + return new PocketAccountMappingReadPlatformServiceImpl(jdbcTemplate, context, pocketRepositoryWrapper, + pocketAccountMappingRepositoryWrapper); + } + + @Bean + @ConditionalOnMissingBean(PocketWritePlatformService.class) + public PocketWritePlatformService pocketWritePlatformService(PlatformSecurityContext context, PocketDataValidator pocketDataValidator, + AccountEntityServiceFactory accountEntityServiceFactory, PocketRepositoryWrapper pocketRepositoryWrapper, + PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper, + PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService) { + return new PocketWritePlatformServiceImpl(context, pocketDataValidator, accountEntityServiceFactory, pocketRepositoryWrapper, + pocketAccountMappingRepositoryWrapper, pocketAccountMappingReadPlatformService); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java index 88675a87101..4f4d7771346 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java @@ -53,7 +53,7 @@ public String createSelfServiceRegistrationRequest(final String apiRequestBodyAs @Produces({ MediaType.APPLICATION_JSON }) public String createSelfServiceUser(final String apiRequestBodyAsJson) { AppUser user = this.selfServiceRegistrationWritePlatformService.createUser(apiRequestBodyAsJson); - return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(user.getId(), null)); + return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(user.getId())); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserApiResourceSwagger.java index 3f3292b8614..1c123de5608 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserApiResourceSwagger.java @@ -52,9 +52,9 @@ private PutSelfUserChanges() {} } @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "6") - public Integer resourceId; + public Long resourceId; public PutSelfUserChanges changes; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResourceSwagger.java index 2ccf0ebbb3f..ef7b0fe2f8a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResourceSwagger.java @@ -61,17 +61,17 @@ private GetSelfUserDetailsRoles() {} @Schema(example = "mifos") public String username; @Schema(example = "1") - public Integer userId; + public Long userId; @Schema(example = "bWlmb3M6cGFzc3dvcmQ=") public String base64EncodedAuthenticationKey; @Schema(example = "true") public Boolean authenticated; @Schema(example = "1") - public Integer officeId; + public Long officeId; @Schema(example = "Head Office") public String officeName; @Schema(example = "1") - public Integer staffId; + public Long staffId; @Schema(example = "Director, Program") public String staffDisplayName; public GetSelfUserDetailsOrganisationalRole organisationalRole; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/api/SelfShareAccountsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/api/SelfShareAccountsApiResourceSwagger.java index 9f9386e84ce..280b3962445 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/api/SelfShareAccountsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/api/SelfShareAccountsApiResourceSwagger.java @@ -207,7 +207,7 @@ private GetShareAccountsShareReferenceId() {} @Schema(example = "Cash in Hand") public String name; @Schema(example = "20301") - public Integer glCode; + public String glCode; } @@ -220,7 +220,7 @@ private GetShareAccountsIncomeFromFeeAccountId() {} @Schema(example = "Other Operating Income") public String name; @Schema(example = "30105") - public Integer glCode; + public String glCode; } @@ -233,7 +233,7 @@ private GetShareAccountsShareEquityId() {} @Schema(example = "Share Equity") public String name; @Schema(example = "00098") - public Integer glCode; + public String glCode; } @@ -246,7 +246,7 @@ private GetShareAccountsShareSuspenseId() {} @Schema(example = "Overpayment Liability") public String name; @Schema(example = "10200") - public Integer glCode; + public String glCode; } @@ -336,7 +336,7 @@ private GetLiabilityAccountTagId() {} @Schema(example = "1") public Integer parentId; @Schema(example = "10104") - public Integer glCode; + public String glCode; @Schema(example = "false") public Boolean disabled; @Schema(example = "true") @@ -399,7 +399,7 @@ private GetAssetAccountTagId() {} @Schema(example = "8") public Integer parentId; @Schema(example = "20101") - public Integer glCode; + public String glCode; @Schema(example = "false") public Boolean disabled; @Schema(example = "true") @@ -460,7 +460,7 @@ private GetIncomeAccountTagId() {} @Schema(example = "Loan Recovery (Temp)") public String name; @Schema(example = "220002-Temp") - public Integer glCode; + public String glCode; @Schema(example = "false") public Boolean disabled; @Schema(example = "true") @@ -524,7 +524,7 @@ private GetShareAccountsEquityAccountTagId() {} @Schema(example = "25") public Integer parentId; @Schema(example = "00098") - public Integer glCode; + public String glCode; @Schema(example = "false") public Boolean disabled; @Schema(example = "true") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformServiceImpl.java index 7594e61594e..249274852bf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformServiceImpl.java @@ -19,20 +19,14 @@ package org.apache.fineract.portfolio.self.shareaccounts.service; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class AppUserShareAccountsMapperReadPlatformServiceImpl implements AppUserShareAccountsMapperReadPlatformService { private final JdbcTemplate jdbcTemplate; - @Autowired - public AppUserShareAccountsMapperReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - @Override public Boolean isShareAccountsMappedToUser(Long accountId, Long appUserId) { return this.jdbcTemplate.queryForObject("select case when (count(*) > 0) then true else false end " diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/starter/SelfShareAccountsConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/starter/SelfShareAccountsConfiguration.java new file mode 100644 index 00000000000..9d64a45791b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/starter/SelfShareAccountsConfiguration.java @@ -0,0 +1,37 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.self.shareaccounts.starter; + +import org.apache.fineract.portfolio.self.shareaccounts.service.AppUserShareAccountsMapperReadPlatformService; +import org.apache.fineract.portfolio.self.shareaccounts.service.AppUserShareAccountsMapperReadPlatformServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class SelfShareAccountsConfiguration { + + @Bean + @ConditionalOnMissingBean(AppUserShareAccountsMapperReadPlatformService.class) + public AppUserShareAccountsMapperReadPlatformService appUserShareAccountsMapperReadPlatformService(JdbcTemplate jdbcTemplate) { + return new AppUserShareAccountsMapperReadPlatformServiceImpl(jdbcTemplate); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccount.java index ffb3e2a07a8..8662e9d6d31 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccount.java @@ -36,6 +36,7 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.portfolio.client.domain.Client; @@ -209,7 +210,7 @@ public ShareProduct getShareProduct() { public boolean setSubmittedDate(final LocalDate submittedDate) { boolean toReturn = false; - if (this.submittedDate.compareTo(submittedDate) == 0 ? Boolean.FALSE : Boolean.TRUE) { + if (!DateUtils.isEqual(submittedDate, this.submittedDate)) { this.submittedDate = submittedDate; toReturn = true; } @@ -218,7 +219,7 @@ public boolean setSubmittedDate(final LocalDate submittedDate) { public boolean setApprovedDate(final LocalDate approvedDate) { boolean toReturn = false; - if (this.approvedDate.compareTo(approvedDate) == 0 ? Boolean.FALSE : Boolean.TRUE) { + if (!DateUtils.isEqual(approvedDate, this.approvedDate)) { this.approvedDate = approvedDate; toReturn = true; } @@ -514,11 +515,11 @@ public void setTotalPendingShares(final Long shares) { public ShareAccountTransaction getShareAccountTransaction(final ShareAccountTransaction transaction) { ShareAccountTransaction returnTrans = null; for (ShareAccountTransaction tran : this.shareAccountTransactions) { - if (tran.getPurchasedDate().compareTo(transaction.getPurchasedDate()) == 0 ? Boolean.TRUE - : Boolean.FALSE && tran.getTotalShares().equals(transaction.getTotalShares()) - && tran.getPurchasePrice().compareTo(transaction.getPurchasePrice()) == 0 ? Boolean.TRUE - : Boolean.FALSE && tran.getTransactionStatus().equals(transaction.getTransactionStatus()) - && tran.getTransactionType().equals(transaction.getTransactionType())) { + if (DateUtils.isEqual(tran.getPurchasedDate(), transaction.getPurchasedDate()) + && tran.getTotalShares().equals(transaction.getTotalShares()) + && tran.getPurchasePrice().compareTo(transaction.getPurchasePrice()) == 0 + && tran.getTransactionStatus().equals(transaction.getTransactionStatus()) + && tran.getTransactionType().equals(transaction.getTransactionType())) { returnTrans = tran; break; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java index a7c16bfce1e..4f457a1dc0c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java @@ -469,7 +469,7 @@ public Map validateAndApprove(JsonCommand jsonCommand, ShareAcco } LocalDate approvedDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.approveddate_paramname, element); final LocalDate submittalDate = account.getSubmittedDate(); - if (approvedDate != null && approvedDate.isBefore(submittalDate)) { + if (approvedDate != null && DateUtils.isBefore(approvedDate, submittalDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(jsonCommand.dateFormat()) .withLocale(jsonCommand.extractLocale()); final String submittalDateAsString = formatter.format(submittalDate); @@ -610,7 +610,7 @@ public Map validateAndActivate(JsonCommand jsonCommand, ShareAcc LocalDate activatedDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.activatedate_paramname, element); baseDataValidator.reset().parameter(ShareAccountApiConstants.activatedate_paramname).value(activatedDate).notNull(); final LocalDate approvedDate = account.getApprovedDate(); - if (activatedDate != null && activatedDate.isBefore(approvedDate)) { + if (activatedDate != null && DateUtils.isBefore(activatedDate, approvedDate)) { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(jsonCommand.dateFormat()) .withLocale(jsonCommand.extractLocale()); final String submittalDateAsString = formatter.format(approvedDate); @@ -710,7 +710,7 @@ public Map validateAndApplyAddtionalShares(JsonCommand jsonComma for (ShareAccountTransaction transaction : transactions) { if (!transaction.isChargeTransaction()) { LocalDate transactionDate = transaction.getPurchasedDate(); - if (requestedDate.isBefore(transactionDate)) { + if (DateUtils.isBefore(requestedDate, transactionDate)) { isTransactionBeforeExistingTransactions = true; break; } @@ -867,7 +867,7 @@ public Map validateAndRedeemShares(JsonCommand jsonCommand, Shar for (ShareAccountTransaction transaction : transactions) { if (!transaction.isChargeTransaction() && transaction.isActive()) { LocalDate transactionDate = transaction.getPurchasedDate(); - if (requestedDate.isBefore(transactionDate)) { + if (DateUtils.isBefore(requestedDate, transactionDate)) { isTransactionBeforeExistingTransactions = true; break; } @@ -908,8 +908,8 @@ private void validateRedeemRequest(final ShareAccount account, ShareAccountTrans if (lockinPeriod == null && periodType == null) { return; } - Long totalSharesCanBeRedeemed = Long.valueOf(0); - Long totalSharesPurchasedBeforeRedeem = Long.valueOf(0); + Long totalSharesCanBeRedeemed = 0L; + Long totalSharesPurchasedBeforeRedeem = 0L; boolean isPurchaseTransactionExist = false; Set transactions = account.getShareAccountTransactions(); @@ -917,7 +917,7 @@ private void validateRedeemRequest(final ShareAccount account, ShareAccountTrans if (transaction.isActive() && !transaction.isChargeTransaction()) { LocalDate purchaseDate = transaction.getPurchasedDate(); LocalDate lockinDate = deriveLockinPeriodDuration(lockinPeriod, periodType, purchaseDate); - if (!lockinDate.isAfter(redeemDate)) { + if (!DateUtils.isAfter(lockinDate, redeemDate)) { if (transaction.isPurchasTransaction()) { totalSharesCanBeRedeemed += transaction.getTotalShares(); } else if (transaction.isRedeemTransaction()) { @@ -925,7 +925,7 @@ private void validateRedeemRequest(final ShareAccount account, ShareAccountTrans } } - if (!purchaseDate.isAfter(redeemDate)) { + if (!DateUtils.isAfter(purchaseDate, redeemDate)) { isPurchaseTransactionExist = true; if (transaction.isPurchasTransaction()) { totalSharesPurchasedBeforeRedeem += transaction.getTotalShares(); @@ -1022,7 +1022,7 @@ public Map validateAndClose(JsonCommand jsonCommand, ShareAccoun for (ShareAccountTransaction transaction : transactions) { if (!transaction.isChargeTransaction()) { LocalDate transactionDate = transaction.getPurchasedDate(); - if (closedDate.isBefore(transactionDate)) { + if (DateUtils.isBefore(closedDate, transactionDate)) { isTransactionBeforeExistingTransactions = true; break; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java index 404ab00a2b8..e9c961ebf0c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java @@ -23,24 +23,18 @@ import java.sql.SQLException; import java.time.LocalDate; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class PurchasedSharesReadPlatformServiceImpl implements PurchasedSharesReadPlatformService { private final JdbcTemplate jdbcTemplate; - @Autowired - public PurchasedSharesReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - @Override public Collection retrievePurchasedShares(Long accountId) { PurchasedSharesDataRowMapper mapper = new PurchasedSharesDataRowMapper(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java index ad6e39597ef..8c43a211a81 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java @@ -22,27 +22,21 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.charge.service.ChargeEnumerations; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountChargeData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ShareAccountChargeReadPlatformServiceImpl implements ShareAccountChargeReadPlatformService { private final JdbcTemplate jdbcTemplate; - @Autowired - public ShareAccountChargeReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - @Override public Collection retrieveAccountCharges(Long accountId, String status) { final ShareAccountChargeMapper rm = new ShareAccountChargeMapper(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java index 04fdadb3f6e..24b52b410e5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java @@ -19,29 +19,21 @@ package org.apache.fineract.portfolio.shareaccounts.service; import com.google.gson.JsonElement; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants; import org.apache.fineract.portfolio.accounts.service.AccountsCommandsService; import org.apache.fineract.portfolio.shareaccounts.serialization.ShareAccountDataSerializer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service(value = "SHAREACCOUNT_COMMANDSERVICE") +@RequiredArgsConstructor public class ShareAccountCommandsServiceImpl implements AccountsCommandsService { private final FromJsonHelper fromApiJsonHelper; private final ShareAccountDataSerializer shareAccountDataSerializer; - @Autowired - public ShareAccountCommandsServiceImpl(final FromJsonHelper fromApiJsonHelper, - final ShareAccountDataSerializer shareAccountDataSerializer) { - this.fromApiJsonHelper = fromApiJsonHelper; - this.shareAccountDataSerializer = shareAccountDataSerializer; - } - @Override public Object handleCommand(Long accountId, String command, String jsonBody) { final JsonElement parsedCommand = this.fromApiJsonHelper.parse(jsonBody); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountDividendReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountDividendReadPlatformServiceImpl.java index 6f03922dbb8..25a36c17f79 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountDividendReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountDividendReadPlatformServiceImpl.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.Page; @@ -35,12 +36,10 @@ import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountDividendData; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendStatusType; import org.apache.fineract.portfolio.shareproducts.domain.ShareProductDividendStatusType; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ShareAccountDividendReadPlatformServiceImpl implements ShareAccountDividendReadPlatformService { private final JdbcTemplate jdbcTemplate; @@ -48,15 +47,6 @@ public class ShareAccountDividendReadPlatformServiceImpl implements ShareAccount private final PaginationHelper paginationHelper; private final DatabaseSpecificSQLGenerator sqlGenerator; - @Autowired - public ShareAccountDividendReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - this.jdbcTemplate = jdbcTemplate; - this.columnValidator = columnValidator; - this.paginationHelper = paginationHelper; - this.sqlGenerator = sqlGenerator; - } - @Override public List> retriveDividendDetailsForPostDividents() { StringBuilder sb = new StringBuilder(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java index d763777b6cb..7124ff7894a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -36,7 +37,6 @@ import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.accountdetails.data.ShareAccountSummaryData; -import org.apache.fineract.portfolio.accounts.constants.AccountsApiConstants; import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants; import org.apache.fineract.portfolio.accounts.data.AccountData; import org.apache.fineract.portfolio.accounts.exceptions.ShareAccountNotFoundException; @@ -62,14 +62,12 @@ import org.apache.fineract.portfolio.shareproducts.data.ShareProductData; import org.apache.fineract.portfolio.shareproducts.data.ShareProductMarketPriceData; import org.apache.fineract.portfolio.shareproducts.service.ShareProductDropdownReadPlatformService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service(value = "share" + AccountsApiConstants.READPLATFORM_NAME) +@RequiredArgsConstructor public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlatformService { private final ApplicationContext applicationContext; @@ -84,27 +82,6 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat private final PaginationHelper shareAccountDataPaginationHelper; private final DatabaseSpecificSQLGenerator sqlGenerator; - @Autowired - public ShareAccountReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final ApplicationContext applicationContext, - final ChargeReadPlatformService chargeReadPlatformService, - final ShareProductDropdownReadPlatformService shareProductDropdownReadPlatformService, - final SavingsAccountReadPlatformService savingsAccountReadPlatformService, - final ClientReadPlatformService clientReadPlatformService, - final ShareAccountChargeReadPlatformService shareAccountChargeReadPlatformService, - final PurchasedSharesReadPlatformService purchasedSharesReadPlatformService, DatabaseSpecificSQLGenerator sqlGenerator, - PaginationHelper paginationHelper) { - this.jdbcTemplate = jdbcTemplate; - this.applicationContext = applicationContext; - this.chargeReadPlatformService = chargeReadPlatformService; - this.shareProductDropdownReadPlatformService = shareProductDropdownReadPlatformService; - this.savingsAccountReadPlatformService = savingsAccountReadPlatformService; - this.clientReadPlatformService = clientReadPlatformService; - this.shareAccountChargeReadPlatformService = shareAccountChargeReadPlatformService; - this.purchasedSharesReadPlatformService = purchasedSharesReadPlatformService; - this.shareAccountDataPaginationHelper = paginationHelper; - this.sqlGenerator = sqlGenerator; - } - @Override public ShareAccountData retrieveTemplate(Long clientId, Long productId) { ShareAccountData toReturn = null; @@ -140,8 +117,8 @@ private BigDecimal deriveMarketPrice(final ShareProductData shareProductData) { if (marketDataSet != null && !marketDataSet.isEmpty()) { LocalDate currentDate = DateUtils.getBusinessLocalDate(); for (ShareProductMarketPriceData data : marketDataSet) { - LocalDate futureDate = data.getFromDate(); - if (currentDate.isAfter(futureDate)) { + LocalDate fromDate = data.getFromDate(); + if (DateUtils.isBefore(fromDate, currentDate)) { marketValue = data.getShareValue(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountSchedularServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountSchedularServiceImpl.java index 9460cc3bc02..70d2c88e994 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountSchedularServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountSchedularServiceImpl.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.shareaccounts.service; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; @@ -26,25 +27,15 @@ import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendDetails; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendRepository; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendStatusType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class ShareAccountSchedularServiceImpl implements ShareAccountSchedularService { private final ShareAccountDividendRepository shareAccountDividendRepository; private final SavingsAccountDomainService savingsAccountDomainService; private final SavingsAccountAssembler savingsAccountAssembler; - @Autowired - public ShareAccountSchedularServiceImpl(final ShareAccountDividendRepository shareAccountDividendRepository, - final SavingsAccountDomainService savingsAccountDomainService, final SavingsAccountAssembler savingsAccountAssembler) { - this.shareAccountDividendRepository = shareAccountDividendRepository; - this.savingsAccountDomainService = savingsAccountDomainService; - this.savingsAccountAssembler = savingsAccountAssembler; - } - @Override @Transactional public void postDividend(final Long dividendDetailId, final Long savingsId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java index 54d495e9bb2..a3aee388114 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; @@ -53,12 +54,10 @@ import org.apache.fineract.portfolio.shareaccounts.serialization.ShareAccountDataSerializer; import org.apache.fineract.portfolio.shareproducts.domain.ShareProduct; import org.apache.fineract.portfolio.shareproducts.domain.ShareProductRepositoryWrapper; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareAccountWritePlatformService { private final ShareAccountDataSerializer accountDataSerializer; @@ -77,22 +76,6 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA private final BusinessEventNotifierService businessEventNotifierService; - @Autowired - public ShareAccountWritePlatformServiceJpaRepositoryImpl(final ShareAccountDataSerializer accountDataSerializer, - final ShareAccountRepositoryWrapper shareAccountRepository, final ShareProductRepositoryWrapper shareProductRepository, - final AccountNumberGenerator accountNumberGenerator, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, - final JournalEntryWritePlatformService journalEntryWritePlatformService, final NoteRepository noteRepository, - final BusinessEventNotifierService businessEventNotifierService) { - this.accountDataSerializer = accountDataSerializer; - this.shareAccountRepository = shareAccountRepository; - this.shareProductRepository = shareProductRepository; - this.accountNumberGenerator = accountNumberGenerator; - this.accountNumberFormatRepository = accountNumberFormatRepository; - this.journalEntryWritePlatformService = journalEntryWritePlatformService; - this.noteRepository = noteRepository; - this.businessEventNotifierService = businessEventNotifierService; - } - @Override public CommandProcessingResult createShareAccount(JsonCommand jsonCommand) { try { @@ -128,7 +111,7 @@ private void generateAccountNumber(final ShareAccount account) { private Map populateJournalEntries(final ShareAccount account, final Set transactions) { final Map accountingBridgeData = new HashMap<>(); - Boolean cashBasedAccounting = account.getShareProduct().getAccountingType() == 2 ? Boolean.TRUE : Boolean.FALSE; + Boolean cashBasedAccounting = account.getShareProduct().getAccountingType() == 2; accountingBridgeData.put("cashBasedAccountingEnabled", cashBasedAccounting); accountingBridgeData.put("accrualBasedAccountingEnabled", Boolean.FALSE); accountingBridgeData.put("shareAccountId", account.getId()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/start/ShareAccountsConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/start/ShareAccountsConfiguration.java new file mode 100644 index 00000000000..cc5d2b38a6e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/start/ShareAccountsConfiguration.java @@ -0,0 +1,123 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.shareaccounts.start; + +import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.portfolio.accounts.constants.AccountsApiConstants; +import org.apache.fineract.portfolio.accounts.service.AccountsCommandsService; +import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; +import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator; +import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService; +import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; +import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendRepository; +import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountRepositoryWrapper; +import org.apache.fineract.portfolio.shareaccounts.serialization.ShareAccountDataSerializer; +import org.apache.fineract.portfolio.shareaccounts.service.PurchasedSharesReadPlatformService; +import org.apache.fineract.portfolio.shareaccounts.service.PurchasedSharesReadPlatformServiceImpl; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountChargeReadPlatformService; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountChargeReadPlatformServiceImpl; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountCommandsServiceImpl; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountDividendReadPlatformService; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountDividendReadPlatformServiceImpl; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformService; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformServiceImpl; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountSchedularService; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountSchedularServiceImpl; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountWritePlatformService; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountWritePlatformServiceJpaRepositoryImpl; +import org.apache.fineract.portfolio.shareproducts.domain.ShareProductRepositoryWrapper; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductDropdownReadPlatformService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class ShareAccountsConfiguration { + + @Bean + @ConditionalOnMissingBean(PurchasedSharesReadPlatformService.class) + public PurchasedSharesReadPlatformService purchasedSharesReadPlatformService(JdbcTemplate jdbcTemplate) { + return new PurchasedSharesReadPlatformServiceImpl(jdbcTemplate); + } + + @Bean + @ConditionalOnMissingBean(ShareAccountChargeReadPlatformService.class) + public ShareAccountChargeReadPlatformService shareAccountChargeReadPlatformService(JdbcTemplate jdbcTemplate) { + return new ShareAccountChargeReadPlatformServiceImpl(jdbcTemplate); + } + + @Bean(value = "SHAREACCOUNT_COMMANDSERVICE") + @ConditionalOnMissingBean(AccountsCommandsService.class) + public AccountsCommandsService accountsCommandsService(FromJsonHelper fromApiJsonHelper, + ShareAccountDataSerializer shareAccountDataSerializer) { + return new ShareAccountCommandsServiceImpl(fromApiJsonHelper, shareAccountDataSerializer); + } + + @Bean + @ConditionalOnMissingBean(ShareAccountDividendReadPlatformService.class) + public ShareAccountDividendReadPlatformService shareAccountDividendReadPlatformService(JdbcTemplate jdbcTemplate, + ColumnValidator columnValidator, PaginationHelper paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator) { + return new ShareAccountDividendReadPlatformServiceImpl(jdbcTemplate, columnValidator, paginationHelper, sqlGenerator); + + } + + @Bean(value = "share" + AccountsApiConstants.READPLATFORM_NAME) + @ConditionalOnMissingBean(ShareAccountReadPlatformService.class) + public ShareAccountReadPlatformService shareAccountReadPlatformService(ApplicationContext applicationContext, + ChargeReadPlatformService chargeReadPlatformService, + ShareProductDropdownReadPlatformService shareProductDropdownReadPlatformService, + SavingsAccountReadPlatformService savingsAccountReadPlatformService, ClientReadPlatformService clientReadPlatformService, + ShareAccountChargeReadPlatformService shareAccountChargeReadPlatformService, + PurchasedSharesReadPlatformService purchasedSharesReadPlatformService, JdbcTemplate jdbcTemplate, + PaginationHelper paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator) { + return new ShareAccountReadPlatformServiceImpl(applicationContext, chargeReadPlatformService, + shareProductDropdownReadPlatformService, savingsAccountReadPlatformService, clientReadPlatformService, + shareAccountChargeReadPlatformService, purchasedSharesReadPlatformService, jdbcTemplate, paginationHelper, sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(ShareAccountSchedularService.class) + public ShareAccountSchedularService shareAccountSchedularService(ShareAccountDividendRepository shareAccountDividendRepository, + SavingsAccountDomainService savingsAccountDomainService, SavingsAccountAssembler savingsAccountAssembler) { + return new ShareAccountSchedularServiceImpl(shareAccountDividendRepository, savingsAccountDomainService, savingsAccountAssembler); + } + + @Bean + @ConditionalOnMissingBean(ShareAccountWritePlatformService.class) + public ShareAccountWritePlatformService shareAccountWritePlatformService(ShareAccountDataSerializer accountDataSerializer, + ShareAccountRepositoryWrapper shareAccountRepository, ShareProductRepositoryWrapper shareProductRepository, + AccountNumberGenerator accountNumberGenerator, AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, + JournalEntryWritePlatformService journalEntryWritePlatformService, NoteRepository noteRepository, + BusinessEventNotifierService businessEventNotifierService) { + return new ShareAccountWritePlatformServiceJpaRepositoryImpl(accountDataSerializer, shareAccountRepository, shareProductRepository, + accountNumberGenerator, accountNumberFormatRepository, journalEntryWritePlatformService, noteRepository, + businessEventNotifierService); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java index 0d6babfd0e6..1caa0f9b51a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java @@ -237,7 +237,7 @@ public MonetaryCurrency getCurrency() { public boolean setUnitPrice(BigDecimal unitPrice) { boolean returnValue = false; - if (this.unitPrice.compareTo(unitPrice) == 0 ? Boolean.FALSE : Boolean.TRUE) { + if (this.unitPrice.compareTo(unitPrice) != 0) { this.unitPrice = unitPrice; returnValue = true; } @@ -353,7 +353,7 @@ public String getShortName() { public boolean setshareCapitalValue(BigDecimal shareCapitalValue) { boolean updated = false; - if (this.shareCapital == null || this.shareCapital.compareTo(shareCapitalValue) == 0 ? Boolean.FALSE : Boolean.TRUE) { + if (this.shareCapital == null || this.shareCapital.compareTo(shareCapitalValue) != 0) { this.shareCapital = shareCapitalValue; updated = true; } @@ -395,8 +395,8 @@ public BigDecimal deriveMarketPrice(final LocalDate currentDate) { BigDecimal marketValue = this.unitPrice; if (this.marketPrice != null && !this.marketPrice.isEmpty()) { for (ShareProductMarketPrice data : this.marketPrice) { - LocalDate futureDate = data.getStartDate(); - if (currentDate.compareTo(futureDate) == 0 ? Boolean.TRUE : Boolean.FALSE || currentDate.isAfter(futureDate)) { + LocalDate startDate = data.getStartDate(); + if (!DateUtils.isAfter(startDate, currentDate)) { marketValue = data.getPrice(); } } @@ -406,7 +406,7 @@ public BigDecimal deriveMarketPrice(final LocalDate currentDate) { public void addSubscribedShares(final Long subscribedShares) { if (this.totalSubscribedShares == null) { - this.totalSubscribedShares = Long.valueOf(0); + this.totalSubscribedShares = 0L; } this.totalSubscribedShares += subscribedShares; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java index 52c6bf3e890..fcc54c495a7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java @@ -19,24 +19,18 @@ package org.apache.fineract.portfolio.shareproducts.service; import com.google.gson.JsonElement; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.products.service.ProductCommandsService; import org.apache.fineract.portfolio.shareproducts.constants.ShareProductApiConstants; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service(value = "SHAREPRODUCT_COMMANDSERVICE") +@RequiredArgsConstructor public class ShareProductCommandsServiceImpl implements ProductCommandsService { private final FromJsonHelper fromApiJsonHelper; - @Autowired - public ShareProductCommandsServiceImpl(final FromJsonHelper fromApiJsonHelper) { - this.fromApiJsonHelper = fromApiJsonHelper; - } - public CommandProcessingResult postDividends(Long productId, JsonCommand jsonCommand) { return null; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java index ca76bf94a9b..57e444b6fb6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java @@ -24,6 +24,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.products.service.ShareProductReadPlatformService; @@ -35,22 +37,13 @@ import org.apache.fineract.portfolio.shareproducts.data.ShareProductData; import org.apache.fineract.portfolio.shareproducts.domain.ShareProductDividendPayOutDetails; import org.apache.fineract.portfolio.shareproducts.exception.ShareAccountsNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -@Component +@RequiredArgsConstructor public class ShareProductDividendAssembler { private final ShareProductReadPlatformService shareProductReadPlatformService; private final ShareAccountReadPlatformService shareAccountReadPlatformService; - @Autowired - public ShareProductDividendAssembler(final ShareProductReadPlatformService shareProductReadPlatformService, - final ShareAccountReadPlatformService shareAccountReadPlatformService) { - this.shareProductReadPlatformService = shareProductReadPlatformService; - this.shareAccountReadPlatformService = shareAccountReadPlatformService; - } - public ShareProductDividendPayOutDetails calculateDividends(final Long productId, final BigDecimal amount, final LocalDate dividendPeriodStartDate, final LocalDate dividendPeriodEndDate) { @@ -106,7 +99,7 @@ private long calculateNumberOfShareDays(final LocalDate postingDate, final Local if (status.isApproved() && !type.isChargePayment()) { LocalDate shareStartDate = purchasedSharesData.getPurchasedDate(); - if (shareStartDate.isBefore(lastDividendPostDate)) { + if (DateUtils.isBefore(shareStartDate, lastDividendPostDate)) { shareStartDate = lastDividendPostDate; } int numberOfPurchseDays = Math.toIntExact(ChronoUnit.DAYS.between(shareStartDate, postingDate)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendReadPlatformServiceImpl.java index 2c7fc774344..2e8bc0b6cd9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendReadPlatformServiceImpl.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.Page; @@ -36,12 +37,10 @@ import org.apache.fineract.portfolio.shareaccounts.service.SharesEnumerations; import org.apache.fineract.portfolio.shareproducts.data.ShareProductData; import org.apache.fineract.portfolio.shareproducts.data.ShareProductDividendPayOutData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ShareProductDividendReadPlatformServiceImpl implements ShareProductDividendReadPlatformService { private final JdbcTemplate jdbcTemplate; @@ -49,15 +48,6 @@ public class ShareProductDividendReadPlatformServiceImpl implements ShareProduct private final PaginationHelper paginationHelper; private final DatabaseSpecificSQLGenerator sqlGenerator; - @Autowired - public ShareProductDividendReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - this.jdbcTemplate = jdbcTemplate; - this.columnValidator = columnValidator; - this.paginationHelper = paginationHelper; - this.sqlGenerator = sqlGenerator; - } - @Override public Page retriveAll(final Long productId, final Integer status, final SearchParameters searchParameters) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDropdownReadPlatformServiceImpl.java index e2800e96538..620ea3a2b66 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDropdownReadPlatformServiceImpl.java @@ -21,12 +21,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import lombok.NoArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.shareaccounts.service.SharesEnumerations; import org.apache.fineract.portfolio.shareproducts.SharePeriodFrequencyType; -import org.springframework.stereotype.Service; -@Service +@NoArgsConstructor public class ShareProductDropdownReadPlatformServiceImpl implements ShareProductDropdownReadPlatformService { @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductReadPlatformServiceImpl.java index 2e2d5b9f87c..7b3ac127491 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductReadPlatformServiceImpl.java @@ -52,9 +52,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service(value = "shareReadPlatformService") @RequiredArgsConstructor public class ShareProductReadPlatformServiceImpl implements ShareProductReadPlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductWritePlatformServiceJpaRepositoryImpl.java index e22aed95033..fa7e3ba5ddd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductWritePlatformServiceJpaRepositoryImpl.java @@ -43,9 +43,7 @@ import org.apache.fineract.portfolio.shareproducts.serialization.ShareProductDataSerializer; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; -@Service @Slf4j @RequiredArgsConstructor public class ShareProductWritePlatformServiceJpaRepositoryImpl implements ShareProductWritePlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/starter/ShareProductsConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/starter/ShareProductsConfiguration.java new file mode 100644 index 00000000000..2cbe6469d5c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/starter/ShareProductsConfiguration.java @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.shareproducts.starter; + +import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService; +import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService; +import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; +import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; +import org.apache.fineract.portfolio.products.service.ShareProductReadPlatformService; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformService; +import org.apache.fineract.portfolio.shareproducts.domain.ShareProductDividentPayOutDetailsRepositoryWrapper; +import org.apache.fineract.portfolio.shareproducts.domain.ShareProductRepositoryWrapper; +import org.apache.fineract.portfolio.shareproducts.serialization.ShareProductDataSerializer; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductCommandsServiceImpl; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductDividendAssembler; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductDividendReadPlatformService; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductDividendReadPlatformServiceImpl; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductDropdownReadPlatformService; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductDropdownReadPlatformServiceImpl; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductReadPlatformServiceImpl; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductWritePlatformService; +import org.apache.fineract.portfolio.shareproducts.service.ShareProductWritePlatformServiceJpaRepositoryImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class ShareProductsConfiguration { + + @Bean(value = "SHAREPRODUCT_COMMANDSERVICE") + @ConditionalOnMissingBean(ShareProductCommandsServiceImpl.class) + public ShareProductCommandsServiceImpl shareProductCommandsService(FromJsonHelper fromApiJsonHelper) { + return new ShareProductCommandsServiceImpl(fromApiJsonHelper); + + } + + @Bean + @ConditionalOnMissingBean(ShareProductDividendAssembler.class) + public ShareProductDividendAssembler shareProductDividendAssembler(ShareProductReadPlatformService shareProductReadPlatformService, + ShareAccountReadPlatformService shareAccountReadPlatformService) { + return new ShareProductDividendAssembler(shareProductReadPlatformService, shareAccountReadPlatformService); + + } + + @Bean + @ConditionalOnMissingBean(ShareProductDividendReadPlatformService.class) + public ShareProductDividendReadPlatformService shareProductDividendReadPlatformService(JdbcTemplate jdbcTemplate, + ColumnValidator columnValidator, PaginationHelper paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator) { + return new ShareProductDividendReadPlatformServiceImpl(jdbcTemplate, columnValidator, paginationHelper, sqlGenerator); + + } + + @Bean + @ConditionalOnMissingBean(ShareProductDropdownReadPlatformService.class) + public ShareProductDropdownReadPlatformService shareProductDropdownReadPlatformService() { + return new ShareProductDropdownReadPlatformServiceImpl(); + } + + @Bean(value = "shareReadPlatformService") + @ConditionalOnMissingBean(ShareProductReadPlatformService.class) + public ShareProductReadPlatformService shareProductReadPlatformService(JdbcTemplate jdbcTemplate, + CurrencyReadPlatformService currencyReadPlatformService, ChargeReadPlatformService chargeReadPlatformService, + ShareProductDropdownReadPlatformService shareProductDropdownReadPlatformService, + AccountingDropdownReadPlatformService accountingDropdownReadPlatformService, + ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService, + PaginationHelper shareProductDataPaginationHelper, DatabaseSpecificSQLGenerator sqlGenerator) { + return new ShareProductReadPlatformServiceImpl(jdbcTemplate, currencyReadPlatformService, chargeReadPlatformService, + shareProductDropdownReadPlatformService, accountingDropdownReadPlatformService, accountMappingReadPlatformService, + shareProductDataPaginationHelper, sqlGenerator); + } + + @Bean + @ConditionalOnMissingBean(ShareProductWritePlatformService.class) + public ShareProductWritePlatformService shareProductWritePlatformService(ShareProductRepositoryWrapper repository, + ShareProductDataSerializer serializer, FromJsonHelper fromApiJsonHelper, + ShareProductDividentPayOutDetailsRepositoryWrapper shareProductDividentPayOutDetailsRepository, + ShareProductDividendAssembler shareProductDividendAssembler, + ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService, + BusinessEventNotifierService businessEventNotifierService) { + return new ShareProductWritePlatformServiceJpaRepositoryImpl(repository, serializer, fromApiJsonHelper, + shareProductDividentPayOutDetailsRepository, shareProductDividendAssembler, accountMappingWritePlatformService, + businessEventNotifierService); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxComponentApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxComponentApiResourceSwagger.java index 3eadbb6fd89..1ac4cf76ab9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxComponentApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxComponentApiResourceSwagger.java @@ -64,7 +64,7 @@ private GetTaxesComponentsHistories() {} } @Schema(example = "1") - public Integer id; + public Long id; @Schema(example = "tax component 1") public String name; @Schema(example = "10.000000") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxGroupApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxGroupApiResourceSwagger.java index 1a3c4a0f9bf..d08764a1e50 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxGroupApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxGroupApiResourceSwagger.java @@ -44,20 +44,20 @@ static final class GetTaxesGroupTaxComponent { private GetTaxesGroupTaxComponent() {} @Schema(example = "7") - public Integer id; + public Long id; @Schema(example = "tax component 2") public String name; } @Schema(example = "7") - public Integer id; + public Long id; public GetTaxesGroupTaxComponent taxComponent; @Schema(example = "[2016, 4, 11]") public LocalDate startDate; } @Schema(example = "7") - public Integer id; + public Long id; @Schema(example = "tax group 1") public String name; public Set taxAssociations; @@ -73,7 +73,7 @@ static final class PostTaxesGroupTaxComponents { private PostTaxesGroupTaxComponents() {} @Schema(example = "7") - public Integer taxComponentId; + public Long taxComponentId; @Schema(example = "11 April 2016") public String startDate; } @@ -106,9 +106,9 @@ static final class PutTaxesGroupTaxComponents { private PutTaxesGroupTaxComponents() {} @Schema(example = "7") - public Integer id; + public Long id; @Schema(example = "7") - public Integer taxComponentId; + public Long taxComponentId; @Schema(example = "22 April 2016") public String endDate; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/serialization/TaxValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/serialization/TaxValidator.java index 0613a86299e..cbb103ff598 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/serialization/TaxValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/serialization/TaxValidator.java @@ -282,7 +282,7 @@ public void validateTaxGroupEndDateAndTaxComponent(final TaxGroup taxGroup, fina for (TaxGroupMappings mapping : groupMappings) { if (mapping.getId() != null) { TaxGroupMappings existing = taxGroup.findOneBy(mapping); - if (existing.endDate() != null && mapping.endDate() != null && !existing.endDate().isEqual(mapping.endDate())) { + if (existing.endDate() != null && mapping.endDate() != null && !DateUtils.isEqual(existing.endDate(), mapping.endDate())) { baseDataValidator.reset().parameter(TaxApiConstants.endDateParamName) .failWithCode("can.not.modify.end.date.once.updated"); } else { @@ -335,7 +335,7 @@ private void validateOverlappingComponents(final Set taxMappin if (groupMappingsOne.getTaxComponent().equals(groupMappings.getTaxComponent())) { if (groupMappingsOne.endDate() == null && groupMappings.endDate() == null) { baseDataValidator.reset().parameter(COMPONENT).failWithCode(DATES_ARE_OVERLAPPING); - } else if (groupMappingsOne.startDate().isAfter(groupMappings.startDate())) { + } else if (DateUtils.isAfter(groupMappingsOne.startDate(), groupMappings.startDate())) { baseDataValidator.reset().parameter(COMPONENT_START_DATE).value(groupMappingsOne.startDate()) .validateDateAfter(groupMappings.endDate()); } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxAssembler.java index 7e226838e12..ac1c1e5d732 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxAssembler.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.accounting.glaccount.domain.GLAccount; import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper; import org.apache.fineract.accounting.glaccount.domain.GLAccountType; @@ -42,24 +43,14 @@ import org.apache.fineract.portfolio.tax.domain.TaxComponentRepositoryWrapper; import org.apache.fineract.portfolio.tax.domain.TaxGroup; import org.apache.fineract.portfolio.tax.domain.TaxGroupMappings; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -@Component +@RequiredArgsConstructor public class TaxAssembler { private final FromJsonHelper fromApiJsonHelper; private final GLAccountRepositoryWrapper glAccountRepositoryWrapper; private final TaxComponentRepositoryWrapper taxComponentRepositoryWrapper; - @Autowired - public TaxAssembler(final FromJsonHelper fromApiJsonHelper, final GLAccountRepositoryWrapper glAccountRepositoryWrapper, - final TaxComponentRepositoryWrapper taxComponentRepositoryWrapper) { - this.fromApiJsonHelper = fromApiJsonHelper; - this.glAccountRepositoryWrapper = glAccountRepositoryWrapper; - this.taxComponentRepositoryWrapper = taxComponentRepositoryWrapper; - } - public TaxComponent assembleTaxComponentFrom(final JsonCommand command) { final JsonElement element = command.parsedJson(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformServiceImpl.java index 721d18663ae..67bedc00aac 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformServiceImpl.java @@ -24,6 +24,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; +import lombok.RequiredArgsConstructor; import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService; import org.apache.fineract.accounting.common.AccountingEnumerations; import org.apache.fineract.accounting.glaccount.data.GLAccountData; @@ -33,12 +34,10 @@ import org.apache.fineract.portfolio.tax.data.TaxComponentHistoryData; import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.apache.fineract.portfolio.tax.data.TaxGroupMappingsData; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class TaxReadPlatformServiceImpl implements TaxReadPlatformService { private static final TaxComponentMapper TAX_COMPONENT_MAPPER = new TaxComponentMapper(); @@ -49,13 +48,6 @@ public class TaxReadPlatformServiceImpl implements TaxReadPlatformService { private final JdbcTemplate jdbcTemplate; private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; - @Autowired - public TaxReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, - final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) { - this.jdbcTemplate = jdbcTemplate; - this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService; - } - @Override public Collection retrieveAllTaxComponents() { String sql = "select " + TAX_COMPONENT_MAPPER.getSchema(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformServiceImpl.java index 6ccdcf5a1c8..633f869fbb9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformServiceImpl.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; @@ -31,10 +32,8 @@ import org.apache.fineract.portfolio.tax.domain.TaxGroupRepository; import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper; import org.apache.fineract.portfolio.tax.serialization.TaxValidator; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class TaxWritePlatformServiceImpl implements TaxWritePlatformService { private final TaxValidator validator; @@ -44,18 +43,6 @@ public class TaxWritePlatformServiceImpl implements TaxWritePlatformService { private final TaxGroupRepository taxGroupRepository; private final TaxGroupRepositoryWrapper taxGroupRepositoryWrapper; - @Autowired - public TaxWritePlatformServiceImpl(final TaxValidator validator, final TaxAssembler taxAssembler, - final TaxComponentRepository taxComponentRepository, final TaxGroupRepository taxGroupRepository, - final TaxComponentRepositoryWrapper taxComponentRepositoryWrapper, final TaxGroupRepositoryWrapper taxGroupRepositoryWrapper) { - this.validator = validator; - this.taxAssembler = taxAssembler; - this.taxComponentRepository = taxComponentRepository; - this.taxGroupRepository = taxGroupRepository; - this.taxComponentRepositoryWrapper = taxComponentRepositoryWrapper; - this.taxGroupRepositoryWrapper = taxGroupRepositoryWrapper; - } - @Override public CommandProcessingResult createTaxComponent(final JsonCommand command) { this.validator.validateForTaxComponentCreate(command.json()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/starter/TaxConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/starter/TaxConfiguration.java new file mode 100644 index 00000000000..4c1effb1e89 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/starter/TaxConfiguration.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.tax.starter; + +import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService; +import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.tax.domain.TaxComponentRepository; +import org.apache.fineract.portfolio.tax.domain.TaxComponentRepositoryWrapper; +import org.apache.fineract.portfolio.tax.domain.TaxGroupRepository; +import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper; +import org.apache.fineract.portfolio.tax.serialization.TaxValidator; +import org.apache.fineract.portfolio.tax.service.TaxAssembler; +import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; +import org.apache.fineract.portfolio.tax.service.TaxReadPlatformServiceImpl; +import org.apache.fineract.portfolio.tax.service.TaxWritePlatformService; +import org.apache.fineract.portfolio.tax.service.TaxWritePlatformServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class TaxConfiguration { + + @Bean + @ConditionalOnMissingBean(TaxAssembler.class) + public TaxAssembler taxAssembler(FromJsonHelper fromApiJsonHelper, GLAccountRepositoryWrapper glAccountRepositoryWrapper, + TaxComponentRepositoryWrapper taxComponentRepositoryWrapper) { + return new TaxAssembler(fromApiJsonHelper, glAccountRepositoryWrapper, taxComponentRepositoryWrapper); + } + + @Bean + @ConditionalOnMissingBean(TaxReadPlatformService.class) + public TaxReadPlatformService taxReadPlatformService(JdbcTemplate jdbcTemplate, + AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) { + return new TaxReadPlatformServiceImpl(jdbcTemplate, accountingDropdownReadPlatformService); + } + + @Bean + @ConditionalOnMissingBean(TaxWritePlatformService.class) + public TaxWritePlatformService taxWritePlatformService(TaxValidator validator, TaxAssembler taxAssembler, + TaxComponentRepository taxComponentRepository, TaxGroupRepository taxGroupRepository, + TaxComponentRepositoryWrapper taxComponentRepositoryWrapper, TaxGroupRepositoryWrapper taxGroupRepositoryWrapper) { + return new TaxWritePlatformServiceImpl(validator, taxAssembler, taxComponentRepository, taxComponentRepositoryWrapper, + taxGroupRepository, taxGroupRepositoryWrapper); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java index 8e98cf8ae6c..aea94a2a045 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java @@ -23,6 +23,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; @@ -62,11 +63,9 @@ import org.apache.fineract.portfolio.transfer.exception.ClientNotAwaitingTransferApprovalOrOnHoldException; import org.apache.fineract.portfolio.transfer.exception.TransferNotSupportedException; import org.apache.fineract.portfolio.transfer.exception.TransferNotSupportedException.TransferNotSupportedReason; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWritePlatformService { private final ClientRepositoryWrapper clientRepositoryWrapper; @@ -83,31 +82,6 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr private final ClientTransferDetailsRepositoryWrapper clientTransferDetailsRepositoryWrapper; private final PlatformSecurityContext context; - @Autowired - public TransferWritePlatformServiceJpaRepositoryImpl(final ClientRepositoryWrapper clientRepositoryWrapper, - final OfficeRepositoryWrapper officeRepository, final CalendarInstanceRepository calendarInstanceRepository, - final LoanWritePlatformService loanWritePlatformService, final GroupRepositoryWrapper groupRepository, - final LoanRepositoryWrapper loanRepositoryWrapper, final TransfersDataValidator transfersDataValidator, - final NoteWritePlatformService noteWritePlatformService, final StaffRepositoryWrapper staffRepositoryWrapper, - final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, - final SavingsAccountWritePlatformService savingsAccountWritePlatformService, - final ClientTransferDetailsRepositoryWrapper clientTransferDetailsRepositoryWrapper, final PlatformSecurityContext context) { - this.clientRepositoryWrapper = clientRepositoryWrapper; - this.officeRepository = officeRepository; - this.calendarInstanceRepository = calendarInstanceRepository; - this.loanWritePlatformService = loanWritePlatformService; - this.groupRepository = groupRepository; - this.loanRepositoryWrapper = loanRepositoryWrapper; - this.transfersDataValidator = transfersDataValidator; - this.noteWritePlatformService = noteWritePlatformService; - this.staffRepositoryWrapper = staffRepositoryWrapper; - this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper; - this.savingsAccountWritePlatformService = savingsAccountWritePlatformService; - this.clientTransferDetailsRepositoryWrapper = clientTransferDetailsRepositoryWrapper; - this.context = context; - - } - @Override @Transactional public CommandProcessingResult transferClientsBetweenGroups(final Long sourceGroupId, final JsonCommand jsonCommand) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/starter/TransferConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/starter/TransferConfiguration.java new file mode 100644 index 00000000000..d83e630b528 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/starter/TransferConfiguration.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.transfer.starter; + +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; +import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper; +import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.client.domain.ClientTransferDetailsRepositoryWrapper; +import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService; +import org.apache.fineract.portfolio.note.service.NoteWritePlatformService; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; +import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.apache.fineract.portfolio.transfer.data.TransfersDataValidator; +import org.apache.fineract.portfolio.transfer.service.TransferWritePlatformService; +import org.apache.fineract.portfolio.transfer.service.TransferWritePlatformServiceJpaRepositoryImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TransferConfiguration { + + @Bean + @ConditionalOnMissingBean(TransferWritePlatformService.class) + public TransferWritePlatformService transferWritePlatformService(ClientRepositoryWrapper clientRepositoryWrapper, + OfficeRepositoryWrapper officeRepository, CalendarInstanceRepository calendarInstanceRepository, + LoanWritePlatformService loanWritePlatformService, GroupRepositoryWrapper groupRepository, + LoanRepositoryWrapper loanRepositoryWrapper, TransfersDataValidator transfersDataValidator, + NoteWritePlatformService noteWritePlatformService, StaffRepositoryWrapper staffRepositoryWrapper, + SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, + SavingsAccountWritePlatformService savingsAccountWritePlatformService, + ClientTransferDetailsRepositoryWrapper clientTransferDetailsRepositoryWrapper, PlatformSecurityContext context) { + return new TransferWritePlatformServiceJpaRepositoryImpl(clientRepositoryWrapper, officeRepository, calendarInstanceRepository, + groupRepository, loanWritePlatformService, savingsAccountWritePlatformService, loanRepositoryWrapper, + savingsAccountRepositoryWrapper, transfersDataValidator, noteWritePlatformService, staffRepositoryWrapper, + clientTransferDetailsRepositoryWrapper, context); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/service/LookupTableService.java b/fineract-provider/src/main/java/org/apache/fineract/spm/service/LookupTableService.java index f98d4ab42bf..13c18dd6251 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/spm/service/LookupTableService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/spm/service/LookupTableService.java @@ -19,26 +19,18 @@ package org.apache.fineract.spm.service; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.spm.domain.LookupTable; import org.apache.fineract.spm.domain.LookupTableRepository; import org.apache.fineract.spm.domain.Survey; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class LookupTableService { private final PlatformSecurityContext securityContext; private final LookupTableRepository lookupTableRepository; - @Autowired - public LookupTableService(final PlatformSecurityContext securityContext, final LookupTableRepository lookupTableRepository) { - - this.securityContext = securityContext; - this.lookupTableRepository = lookupTableRepository; - } - public List findByKey(final String key) { this.securityContext.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardReadPlatformServiceImpl.java index a1e1ce863b5..d458ff12c05 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardReadPlatformServiceImpl.java @@ -23,27 +23,20 @@ import java.time.OffsetDateTime; import java.util.Collection; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.spm.data.ScorecardData; import org.apache.fineract.spm.data.ScorecardValue; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ScorecardReadPlatformServiceImpl implements ScorecardReadPlatformService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - @Autowired - public ScorecardReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - } - private static final class ScorecardMapper implements RowMapper { public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardService.java b/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardService.java index 52b162ca8b6..00297f7bb5c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardService.java @@ -19,27 +19,19 @@ package org.apache.fineract.spm.service; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.spm.domain.Scorecard; import org.apache.fineract.spm.domain.ScorecardRepository; import org.apache.fineract.spm.domain.Survey; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class ScorecardService { private final PlatformSecurityContext securityContext; private final ScorecardRepository scorecardRepository; - @Autowired - public ScorecardService(final PlatformSecurityContext securityContext, final ScorecardRepository scorecardRepository) { - - this.securityContext = securityContext; - this.scorecardRepository = scorecardRepository; - } - public List createScorecard(final List scorecards) { this.securityContext.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/service/SpmService.java b/fineract-provider/src/main/java/org/apache/fineract/spm/service/SpmService.java index 22c74184681..a65f21418cf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/spm/service/SpmService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/spm/service/SpmService.java @@ -22,6 +22,7 @@ import jakarta.persistence.PersistenceException; import java.time.LocalDate; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; @@ -29,27 +30,16 @@ import org.apache.fineract.spm.domain.SurveyRepository; import org.apache.fineract.spm.domain.SurveyValidator; import org.apache.fineract.spm.exception.SurveyNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.stereotype.Service; -@Service +@RequiredArgsConstructor public class SpmService { private final PlatformSecurityContext securityContext; private final SurveyRepository surveyRepository; private final SurveyValidator surveyValidator; - @Autowired - public SpmService(final PlatformSecurityContext securityContext, final SurveyRepository surveyRepository, - final SurveyValidator surveyValidator) { - - this.securityContext = securityContext; - this.surveyRepository = surveyRepository; - this.surveyValidator = surveyValidator; - } - public List fetchValidSurveys() { this.securityContext.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/starter/SpmConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/spm/starter/SpmConfiguration.java new file mode 100644 index 00000000000..6e009e9b02d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/spm/starter/SpmConfiguration.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.spm.starter; + +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.spm.domain.LookupTableRepository; +import org.apache.fineract.spm.domain.ScorecardRepository; +import org.apache.fineract.spm.domain.SurveyRepository; +import org.apache.fineract.spm.domain.SurveyValidator; +import org.apache.fineract.spm.service.LookupTableService; +import org.apache.fineract.spm.service.ScorecardReadPlatformService; +import org.apache.fineract.spm.service.ScorecardReadPlatformServiceImpl; +import org.apache.fineract.spm.service.ScorecardService; +import org.apache.fineract.spm.service.SpmService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class SpmConfiguration { + + @Bean + @ConditionalOnMissingBean(LookupTableService.class) + public LookupTableService lookupTableService(PlatformSecurityContext securityContext, LookupTableRepository lookupTableRepository) { + return new LookupTableService(securityContext, lookupTableRepository); + } + + @Bean + @ConditionalOnMissingBean(ScorecardReadPlatformService.class) + public ScorecardReadPlatformService scorecardReadPlatformService(JdbcTemplate jdbcTemplate, PlatformSecurityContext context) { + return new ScorecardReadPlatformServiceImpl(jdbcTemplate, context); + } + + @Bean + @ConditionalOnMissingBean(ScorecardService.class) + public ScorecardService scorecardService(PlatformSecurityContext securityContext, ScorecardRepository scorecardRepository) { + + return new ScorecardService(securityContext, scorecardRepository); + } + + @Bean + @ConditionalOnMissingBean(SpmService.class) + public SpmService spmService(PlatformSecurityContext securityContext, SurveyRepository surveyRepository, + SurveyValidator surveyValidator) { + return new SpmService(securityContext, surveyRepository, surveyValidator); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java index cb6e9efe479..9fe12fe1aa9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java @@ -22,6 +22,7 @@ import com.google.gson.JsonElement; import java.util.ArrayList; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; @@ -31,11 +32,9 @@ import org.apache.fineract.template.domain.TemplateRepository; import org.apache.fineract.template.domain.TemplateType; import org.apache.fineract.template.exception.TemplateNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service +@RequiredArgsConstructor public class JpaTemplateDomainService implements TemplateDomainService { private static final String PROPERTY_NAME = "name"; @@ -44,8 +43,7 @@ public class JpaTemplateDomainService implements TemplateDomainService { private static final String PROPERTY_ENTITY = "entity"; private static final String PROPERTY_TYPE = "type"; - @Autowired - private TemplateRepository templateRepository; + private final TemplateRepository templateRepository; @Override public List