FINERACT-1932: Fineract modularization - fineract-cob

This commit is contained in:
Jose Alberto Hernandez
2025-07-29 21:46:39 +02:00
committed by Arnold Gálovics
parent e090da2a01
commit 7452ea2552
85 changed files with 458 additions and 130 deletions
+2
View File
@@ -28,6 +28,7 @@ buildscript {
[
'fineract-api',
'fineract-core',
'fineract-cob',
'fineract-validation',
'fineract-command',
'fineract-accounting',
@@ -58,6 +59,7 @@ buildscript {
'fineract-api',
'fineract-client',
'fineract-core',
'fineract-cob',
'fineract-validation',
'fineract-command',
'fineract-accounting',
+1
View File
@@ -19,6 +19,7 @@
dependencies {
implementation(project(':fineract-core'))
implementation(project(':fineract-cob'))
implementation(project(':fineract-loan'))
implementation(project(':fineract-provider'))
implementation('org.springframework.boot:spring-boot-starter-data-jpa')
@@ -19,6 +19,7 @@
dependencies {
implementation(project(':fineract-core'))
implementation(project(':fineract-cob'))
implementation(project(':fineract-loan'))
implementation(project(':fineract-provider'))
implementation(project(':custom:acme:loan:cob'))
+76
View File
@@ -0,0 +1,76 @@
/**
* 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.
*/
description = 'Fineract COB'
apply plugin: 'java'
apply plugin: 'eclipse'
configurations {
providedRuntime // needed for Spring Boot executable WAR
providedCompile
compile() {
exclude module: 'hibernate-entitymanager'
exclude module: 'hibernate-validator'
exclude module: 'activation'
exclude module: 'bcmail-jdk14'
exclude module: 'bcprov-jdk14'
exclude module: 'bctsp-jdk14'
exclude module: 'c3p0'
exclude module: 'stax-api'
exclude module: 'jaxb-api'
exclude module: 'jaxb-impl'
exclude module: 'jboss-logging'
exclude module: 'itext-rtf'
exclude module: 'classworlds'
}
runtime
}
apply from: 'dependencies.gradle'
compileJava {
dependsOn ':fineract-avro-schemas:buildJavaSdk'
options.compilerArgs += ['-parameters']
}
// Configuration for the modernizer plugin
// https://github.com/andygoossens/gradle-modernizer-plugin
modernizer {
ignoreClassNamePatterns = [
'.*AbstractPersistableCustom',
'.*EntityTables',
'.*domain.*'
]
}
// If we are running Gradle within Eclipse to enhance classes with OpenJPA,
// set the classes directory to point to Eclipse's default build directory
if (project.hasProperty('env') && project.getProperty('env') == 'eclipse') {
sourceSets.main.java.outputDir = new File(rootProject.projectDir, "fineract-cob/bin/main")
}
if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) {
sourceSets {
test {
java {
exclude '**/core/boot/tests/**'
}
}
}
}
+133
View File
@@ -0,0 +1,133 @@
/**
* 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.
*/
dependencies {
// Never use "compile" scope, but make all dependencies either 'implementation', 'runtimeOnly' or 'testCompile'.
// Note that we never use 'api', because Fineract at least currently is a simple monolithic application ("WAR"), not a library.
// We also (normally should have) no need to ever use 'compileOnly'.
implementation(project(path: ':fineract-avro-schemas'))
implementation(project(path: ':fineract-command'))
implementation(project(path: ':fineract-validation'))
implementation(project(path: ':fineract-core'))
// implementation dependencies are directly used (compiled against) in src/main (and src/test)
implementation(
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-security',
'org.springframework.boot:spring-boot-starter-validation',
'org.springframework.boot:spring-boot-starter-batch',
'org.springframework.batch:spring-batch-integration',
'jakarta.ws.rs:jakarta.ws.rs-api',
'org.glassfish.jersey.media:jersey-media-multipart',
'org.apache.avro:avro',
'com.google.guava:guava',
'com.google.code.gson:gson',
'org.apache.commons:commons-lang3',
'com.jayway.jsonpath:json-path',
'com.github.spotbugs:spotbugs-annotations',
'io.swagger.core.v3:swagger-annotations-jakarta',
'com.squareup.retrofit2:converter-gson',
'org.springdoc:springdoc-openapi-starter-webmvc-ui',
'org.mapstruct:mapstruct',
'io.github.resilience4j:resilience4j-spring-boot3',
'org.apache.httpcomponents:httpcore',
)
implementation ('org.springframework.boot:spring-boot-starter-data-jpa') {
exclude group: 'org.hibernate'
}
implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') {
exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence'
}
implementation('org.springframework.boot:spring-boot-starter-jersey') {
exclude group: 'org.glassfish.hk2.external', module: 'aopalliance-repackaged'
exclude group: 'org.glassfish.hk2', module: 'hk2-runlevel'
exclude group: 'org.hibernate.validator', module: 'hibernate-validator'
exclude group: 'jakarta.activation', module: 'jakarta.activation-api'
}
implementation('org.dom4j:dom4j') {
exclude group: 'javax.xml.bind'
}
implementation ('jakarta.xml.bind:jakarta.xml.bind-api') {
exclude group: 'jakarta.activation'
}
implementation ('org.springframework.boot:spring-boot-starter-data-jpa') {
exclude group: 'org.hibernate'
}
implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') {
exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence'
}
runtimeOnly('org.glassfish.jaxb:jaxb-runtime') {
exclude group: 'com.sun.activation'
}
// runtimeOnly dependencies are things that Fineract code has no direct compile time dependency on, but which must be present at run-time
runtimeOnly(
'org.apache.bval:org.apache.bval.bundle',
// Although fineract (at the time of writing) doesn't have any compile time dep. on httpclient,
// it's useful to have this for the Spring Boot TestRestTemplate http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rest-templates-test-utility
'org.apache.httpcomponents:httpclient'
)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor'
implementation 'ch.qos.logback.contrib:logback-json-classic'
implementation 'ch.qos.logback.contrib:logback-jackson'
implementation 'org.codehaus.janino:janino'
implementation 'org.apache.commons:commons-math3'
implementation 'io.github.classgraph:classgraph'
// testCompile dependencies are ONLY used in src/test, not src/main.
// Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly!
//
testImplementation( 'io.github.classgraph:classgraph')
testImplementation ('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'com.jayway.jsonpath', module: 'json-path'
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
exclude group: 'jakarta.activation'
exclude group: 'javax.activation'
exclude group: 'org.skyscreamer'
}
implementation ('org.mnode.ical4j:ical4j') {
exclude group: 'commons-logging'
exclude group: 'javax.activation'
exclude group: 'com.sun.mail', module: 'javax.mail'
}
implementation ('org.quartz-scheduler:quartz') {
exclude group: 'com.zaxxer', module: 'HikariCP-java7'
}
testImplementation ('org.mockito:mockito-inline')
implementation('org.apache.avro:avro')
implementation(
project(path: ':fineract-avro-schemas')
)
}
@@ -0,0 +1,35 @@
/**
* 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.cob;
public class COBConstant {
public static final String BUSINESS_STEPS = "businessSteps";
public static final String BUSINESS_DATE_PARAMETER_NAME = "BusinessDate";
public static final String IS_CATCH_UP_PARAMETER_NAME = "IS_CATCH_UP";
public static final String COB_CUSTOM_JOB_PARAMETER_KEY = "CUSTOM_JOB_PARAMETER_ID";
public static final Long NUMBER_OF_DAYS_BEHIND = 1L;
protected COBConstant() {
}
}
@@ -20,7 +20,7 @@ package org.apache.fineract.cob.data;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
public interface LoanIdAndExternalIdAndAccountNo {
public interface COBIdAndExternalIdAndAccountNo {
Long getId();
@@ -20,7 +20,7 @@ package org.apache.fineract.cob.data;
import java.time.LocalDate;
public interface LoanIdAndLastClosedBusinessDate {
public interface COBIdAndLastClosedBusinessDate {
Long getId();
@@ -29,8 +29,8 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@EqualsAndHashCode
public class LoanCOBParameter {
public class COBParameter {
private Long minLoanId;
private Long maxLoanId;
private Long minAccountId;
private Long maxAccountId;
}
@@ -23,7 +23,7 @@ import lombok.Data;
@Data
@AllArgsConstructor
public class LoanCOBPartition {
public class COBPartition {
private Long minId;
private Long maxId;
@@ -18,13 +18,13 @@
*/
package org.apache.fineract.cob.exceptions;
public class LoanAccountLockCannotBeOverruledException extends RuntimeException {
public class AccountLockCannotBeOverruledException extends RuntimeException {
public LoanAccountLockCannotBeOverruledException(String message) {
public AccountLockCannotBeOverruledException(String message) {
super(message);
}
public LoanAccountLockCannotBeOverruledException(String message, Exception e) {
public AccountLockCannotBeOverruledException(String message, Exception e) {
super(message, e);
}
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exceptionmapper;
package org.apache.fineract.core.exceptionmapper;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exceptionmapper;
package org.apache.fineract.core.exceptionmapper;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<!-- This file is only used for static weaving, nothing more. -->
<!-- You can find the runtime configuration in the JPAConfig class -->
<persistence-unit name="jpa-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- Core Module Entities -->
<class>org.apache.fineract.useradministration.domain.Role</class>
<class>org.apache.fineract.portfolio.fund.domain.Fund</class>
<class>org.apache.fineract.infrastructure.documentmanagement.domain.Image</class>
<class>org.apache.fineract.organisation.workingdays.domain.WorkingDays</class>
<class>org.apache.fineract.useradministration.domain.Permission</class>
<class>org.apache.fineract.useradministration.domain.AppUserClientMapping</class>
<class>org.apache.fineract.commands.domain.CommandSource</class>
<class>org.apache.fineract.useradministration.domain.AppUser</class>
<class>org.apache.fineract.accounting.glaccount.domain.GLAccount</class>
<class>org.apache.fineract.organisation.monetary.domain.OrganisationCurrency</class>
<class>org.apache.fineract.organisation.staff.domain.Staff</class>
<class>org.apache.fineract.portfolio.rate.domain.Rate</class>
<class>org.apache.fineract.organisation.monetary.domain.ApplicationCurrency</class>
<class>org.apache.fineract.portfolio.calendar.domain.CalendarInstance</class>
<class>org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail</class>
<class>org.apache.fineract.portfolio.calendar.domain.Calendar</class>
<class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class>
<class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class>
<class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class>
<class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class>
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
<class>org.apache.fineract.portfolio.group.domain.Group</class>
<class>org.apache.fineract.portfolio.client.domain.Client</class>
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
<class>org.apache.fineract.portfolio.group.domain.GroupLevel</class>
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEvent</class>
<class>org.apache.fineract.organisation.office.domain.Office</class>
<class>org.apache.fineract.organisation.holiday.domain.Holiday</class>
<class>org.apache.fineract.infrastructure.cache.domain.PlatformCache</class>
<class>org.apache.fineract.infrastructure.codes.domain.Code</class>
<class>org.apache.fineract.infrastructure.businessdate.domain.BusinessDate</class>
<class>org.apache.fineract.infrastructure.codes.domain.CodeValue</class>
<!-- Command Module Entities -->
<class>org.apache.fineract.command.persistence.domain.CommandEntity</class>
<class>org.apache.fineract.command.persistence.converter.JsonAttributeConverter</class>
<!-- Validation Module Entities -->
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.weaving" value="static" />
</properties>
</persistence-unit>
</persistence>
@@ -9,7 +9,7 @@ It is very easy to add your own business steps to Fineract's default steps:
.Business Step Interface
[source,java]
----
include::{rootdir}/fineract-core/src/main/java/org/apache/fineract/cob/COBBusinessStep.java[lines=19..]
include::{rootdir}/fineract-cob/src/main/java/org/apache/fineract/cob/COBBusinessStep.java[lines=19..]
----
== Business Step Implementation
+1
View File
@@ -25,6 +25,7 @@ dependencies {
// implementation dependencies are directly used (compiled against) in src/main (and src/test)
//
implementation(project(path: ':fineract-core'))
implementation(project(path: ':fineract-cob'))
implementation(project(path: ':fineract-accounting'))
implementation(project(path: ':fineract-charge'))
implementation(project(path: ':fineract-loan'))
+1
View File
@@ -25,6 +25,7 @@ dependencies {
// implementation dependencies are directly used (compiled against) in src/main (and src/test)
//
implementation(project(path: ':fineract-core'))
implementation(project(path: ':fineract-cob'))
implementation(project(path: ':fineract-accounting'))
implementation(project(path: ':fineract-charge'))
implementation(project(path: ':fineract-rates'))
@@ -23,7 +23,7 @@ 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.cob.exceptions.LoanAccountLockCannotBeOverruledException;
import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.springframework.stereotype.Component;
@@ -31,10 +31,10 @@ import org.springframework.stereotype.Component;
@Provider
@Component
@Slf4j
public class LoanAccountLockCannotBeOverruledExceptionMapper implements ExceptionMapper<LoanAccountLockCannotBeOverruledException> {
public class LoanAccountLockCannotBeOverruledExceptionMapper implements ExceptionMapper<AccountLockCannotBeOverruledException> {
@Override
public Response toResponse(LoanAccountLockCannotBeOverruledException exception) {
public Response toResponse(AccountLockCannotBeOverruledException exception) {
final String globalisationMessageCode = "error.msg.invalid.request.body";
final String defaultUserMessage = exception.getMessage();
log.warn("Exception occurred", ErrorHandler.findMostSpecificException(exception));
@@ -23,9 +23,9 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.LoanDataForExternalTransfer;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -230,11 +230,11 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
List<Long> findIdsByExternalIds(@Param("externalIds") List<ExternalId> externalIds);
@Query(FIND_ALL_LOANS_BEHIND_BY_LOAN_IDS_AND_STATUSES)
List<LoanIdAndLastClosedBusinessDate> findAllLoansBehindByLoanIdsAndStatuses(@Param("cobBusinessDate") LocalDate cobBusinessDate,
List<COBIdAndLastClosedBusinessDate> findAllLoansBehindByLoanIdsAndStatuses(@Param("cobBusinessDate") LocalDate cobBusinessDate,
@Param("loanIds") List<Long> loanIds, @Param("loanStatuses") Collection<LoanStatus> loanStatuses);
@Query(FIND_ALL_LOANS_BEHIND_OR_NULL_BY_LOAN_IDS_AND_STATUSES)
List<LoanIdAndLastClosedBusinessDate> findAllLoansBehindOrNullByLoanIdsAndStatuses(@Param("cobBusinessDate") LocalDate cobBusinessDate,
List<COBIdAndLastClosedBusinessDate> findAllLoansBehindOrNullByLoanIdsAndStatuses(@Param("cobBusinessDate") LocalDate cobBusinessDate,
@Param("loanIds") List<Long> loanIds, @Param("loanStatuses") Collection<LoanStatus> loanStatuses);
@Query(FIND_ALL_LOANS_BY_LAST_CLOSED_BUSINESS_DATE_AND_MIN_AND_MAX_LOAN_ID_AND_STATUSES)
@@ -248,11 +248,11 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
@Param("loanStatuses") Collection<LoanStatus> loanStatuses);
@Query(FIND_OLDEST_COB_PROCESSED_LOAN)
List<LoanIdAndLastClosedBusinessDate> findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate,
List<COBIdAndLastClosedBusinessDate> findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate,
@Param("loanStatuses") Collection<LoanStatus> loanStatuses);
@Query(FIND_ALL_STAYED_LOCKED_BY_COB_BUSINESS_DATE)
List<LoanIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate);
List<COBIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate);
@Query(FIND_ALL_LOAN_IDS_BY_STATUS)
List<Long> findLoanIdByStatus(@Param("loanStatus") LoanStatus loanStatus);
+1
View File
@@ -25,6 +25,7 @@ dependencies {
}
implementation(project(path: ':fineract-core'))
implementation(project(path: ':fineract-cob'))
implementation(project(path: ':fineract-command'))
implementation(project(path: ':fineract-validation'))
implementation(project(path: ':fineract-accounting'))
@@ -35,7 +35,7 @@ import java.time.format.DateTimeFormatter;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.cob.data.COBPartition;
import org.apache.fineract.cob.loan.LoanCOBConstant;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -85,7 +85,7 @@ public class InternalCOBApiResource implements InitializingBean {
public String getCobPartitions(@Context final UriInfo uriInfo, @PathParam("partitionSize") int partitionSize) {
LocalDate businessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.BUSINESS_DATE);
log.info("RetrieveLoanCOBPartitions is called with partitionSize {} for {}", partitionSize, businessDate);
List<LoanCOBPartition> loanCOBPartitions = retrieveLoanIdService.retrieveLoanCOBPartitions(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND,
List<COBPartition> loanCOBPartitions = retrieveLoanIdService.retrieveLoanCOBPartitions(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND,
businessDate, false, partitionSize);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializerForList.serialize(settings, loanCOBPartitions);
@@ -29,7 +29,7 @@ import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.cob.exceptions.LoanLockCannotBeAppliedException;
@@ -61,11 +61,11 @@ public class ApplyLoanLockTasklet implements Tasklet {
throws LoanLockCannotBeAppliedException {
ExecutionContext executionContext = contribution.getStepExecution().getExecutionContext();
long numberOfExecutions = contribution.getStepExecution().getCommitCount();
LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
COBParameter loanCOBParameter = (COBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
List<Long> loanIds;
if (Objects.isNull(loanCOBParameter)
|| (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))
|| (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) {
|| (Objects.isNull(loanCOBParameter.getMinAccountId()) && Objects.isNull(loanCOBParameter.getMaxAccountId()))
|| (loanCOBParameter.getMinAccountId().equals(0L) && loanCOBParameter.getMaxAccountId().equals(0L))) {
loanIds = Collections.emptyList();
} else {
loanIds = new ArrayList<>(
@@ -18,24 +18,20 @@
*/
package org.apache.fineract.cob.loan;
public final class LoanCOBConstant {
import org.apache.fineract.cob.COBConstant;
public final class LoanCOBConstant extends COBConstant {
public static final String JOB_NAME = "LOAN_COB";
public static final String JOB_HUMAN_READABLE_NAME = "Loan COB";
public static final String LOAN_COB_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS";
public static final String LOAN_COB_PARAMETER = "loanCobParameter";
public static final String BUSINESS_STEPS = "businessSteps";
public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep";
public static final String INLINE_LOAN_COB_JOB_NAME = "INLINE_LOAN_COB";
public static final String BUSINESS_DATE_PARAMETER_NAME = "BusinessDate";
public static final String IS_CATCH_UP_PARAMETER_NAME = "IS_CATCH_UP";
public static final String LOAN_IDS_PARAMETER_NAME = "LoanIds";
public static final String LOAN_COB_PARTITIONER_STEP = "Loan COB partition - Step";
public static final String LOAN_COB_CUSTOM_JOB_PARAMETER_KEY = "CUSTOM_JOB_PARAMETER_ID";
public static final Long NUMBER_OF_DAYS_BEHIND = 1L;
private LoanCOBConstant() {
@@ -29,8 +29,8 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.data.BusinessStepNameAndOrder;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.data.COBPartition;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.infrastructure.springbatch.PropertyService;
import org.springframework.batch.core.JobExecution;
@@ -81,13 +81,13 @@ public class LoanCOBPartitioner implements Partitioner {
}
StopWatch sw = new StopWatch();
sw.start();
List<LoanCOBPartition> loanCOBPartitions = new ArrayList<>(
List<COBPartition> 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
if (loanCOBPartitions.size() == 0) {
loanCOBPartitions.add(new LoanCOBPartition(0L, 0L, 1L, 0L));
loanCOBPartitions.add(new COBPartition(0L, 0L, 1L, 0L));
}
log.info(
"LoanCOBPartitioner found {} loans to be processed as part of COB. {} partitions were created using partition size {}. RetrieveLoanCOBPartitions was executed in {} ms.",
@@ -96,15 +96,15 @@ public class LoanCOBPartitioner implements Partitioner {
.collect(Collectors.toMap(l -> PARTITION_PREFIX + l.getPageNo(), l -> createNewPartition(cobBusinessSteps, l)));
}
private long getLoanCount(List<LoanCOBPartition> loanCOBPartitions) {
return loanCOBPartitions.stream().map(LoanCOBPartition::getCount).reduce(0L, Long::sum);
private long getLoanCount(List<COBPartition> loanCOBPartitions) {
return loanCOBPartitions.stream().map(COBPartition::getCount).reduce(0L, Long::sum);
}
private ExecutionContext createNewPartition(Set<BusinessStepNameAndOrder> cobBusinessSteps, LoanCOBPartition loanCOBPartition) {
private ExecutionContext createNewPartition(Set<BusinessStepNameAndOrder> cobBusinessSteps, COBPartition loanCOBPartition) {
ExecutionContext executionContext = new ExecutionContext();
executionContext.put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps);
executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER,
new LoanCOBParameter(loanCOBPartition.getMinId(), loanCOBPartition.getMaxId()));
new COBParameter(loanCOBPartition.getMinId(), loanCOBPartition.getMaxId()));
executionContext.put("partition", PARTITION_PREFIX + loanCOBPartition.getPageNo());
return executionContext;
}
@@ -25,7 +25,7 @@ import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
@@ -53,11 +53,11 @@ public class LoanItemReader extends AbstractLoanItemReader {
@SuppressWarnings({ "unchecked" })
public void beforeStep(@NonNull StepExecution stepExecution) {
ExecutionContext executionContext = stepExecution.getExecutionContext();
LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
COBParameter loanCOBParameter = (COBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
List<Long> loanIds;
if (Objects.isNull(loanCOBParameter)
|| (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))
|| (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) {
|| (Objects.isNull(loanCOBParameter.getMinAccountId()) && Objects.isNull(loanCOBParameter.getMaxAccountId()))
|| (loanCOBParameter.getMinAccountId().equals(0L) && loanCOBParameter.getMaxAccountId().equals(0L))) {
loanIds = Collections.emptyList();
} else {
loanIds = retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter,
@@ -26,10 +26,10 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.data.COBPartition;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
@@ -49,8 +49,7 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdServ
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public List<LoanCOBPartition> retrieveLoanCOBPartitions(Long numberOfDays, LocalDate businessDate, boolean isCatchUp,
int partitionSize) {
public List<COBPartition> retrieveLoanCOBPartitions(Long numberOfDays, LocalDate businessDate, boolean isCatchUp, int partitionSize) {
StringBuilder sql = new StringBuilder();
sql.append("select min(id) as min, max(id) as max, page, count(id) as count from ");
sql.append(" (select floor(((row_number() over(order by id))-1) / :pageSize) as page, t.* from ");
@@ -71,43 +70,43 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdServ
return namedParameterJdbcTemplate.query(sql.toString(), parameters, RetrieveAllNonClosedLoanIdServiceImpl::mapRow);
}
private static LoanCOBPartition mapRow(ResultSet rs, int rowNum) throws SQLException {
return new LoanCOBPartition(rs.getLong("min"), rs.getLong("max"), rs.getLong("page"), rs.getLong("count"));
private static COBPartition mapRow(ResultSet rs, int rowNum) throws SQLException {
return new COBPartition(rs.getLong("min"), rs.getLong("max"), rs.getLong("page"), rs.getLong("count"));
}
@Override
public List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDate(LocalDate businessDate, List<Long> loanIds) {
public List<COBIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDate(LocalDate businessDate, List<Long> loanIds) {
return loanRepository.findAllLoansBehindByLoanIdsAndStatuses(businessDate, loanIds, NON_CLOSED_LOAN_STATUSES);
}
@Override
public List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds) {
public List<COBIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds) {
return loanRepository.findAllLoansBehindOrNullByLoanIdsAndStatuses(businessDate, loanIds, NON_CLOSED_LOAN_STATUSES);
}
@Override
public List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsOldestCobProcessed(LocalDate businessDate) {
public List<COBIdAndLastClosedBusinessDate> retrieveLoanIdsOldestCobProcessed(LocalDate businessDate) {
return loanRepository.findOldestCOBProcessedLoan(businessDate, NON_CLOSED_LOAN_STATUSES);
}
@Override
public List<Long> retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter,
public List<Long> retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(COBParameter loanCOBParameter,
boolean isCatchUp) {
if (isCatchUp) {
return loanRepository.findAllLoansByLastClosedBusinessDateNotNullAndMinAndMaxLoanIdAndStatuses(
loanCOBParameter.getMinLoanId(), loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil
loanCOBParameter.getMinAccountId(), loanCOBParameter.getMaxAccountId(), ThreadLocalContextUtil
.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND),
NON_CLOSED_LOAN_STATUSES);
} else {
return loanRepository.findAllLoansByLastClosedBusinessDateAndMinAndMaxLoanIdAndStatuses(
loanCOBParameter.getMinLoanId(), loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil
loanCOBParameter.getMinAccountId(), loanCOBParameter.getMaxAccountId(), ThreadLocalContextUtil
.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND),
NON_CLOSED_LOAN_STATUSES);
}
}
@Override
public List<LoanIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(LocalDate cobBusinessDate) {
public List<COBIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(LocalDate cobBusinessDate) {
return loanRepository.findAllStayedLockedByCobBusinessDate(cobBusinessDate);
}
@@ -20,24 +20,24 @@ package org.apache.fineract.cob.loan;
import java.time.LocalDate;
import java.util.List;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.data.COBPartition;
import org.springframework.data.repository.query.Param;
public interface RetrieveLoanIdService {
List<LoanCOBPartition> retrieveLoanCOBPartitions(Long numberOfDays, LocalDate businessDate, boolean isCatchUp, int partitionSize);
List<COBPartition> retrieveLoanCOBPartitions(Long numberOfDays, LocalDate businessDate, boolean isCatchUp, int partitionSize);
List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDate(LocalDate businessDate, List<Long> loanIds);
List<COBIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDate(LocalDate businessDate, List<Long> loanIds);
List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds);
List<COBIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds);
List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsOldestCobProcessed(LocalDate businessDate);
List<COBIdAndLastClosedBusinessDate> retrieveLoanIdsOldestCobProcessed(LocalDate businessDate);
List<Long> retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter, boolean isCatchUp);
List<Long> retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(COBParameter loanCOBParameter, boolean isCatchUp);
List<LoanIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate);
List<COBIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate);
}
@@ -23,9 +23,9 @@ import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.LoanAccountStayedLockedData;
import org.apache.fineract.cob.data.LoanAccountsStayedLockedData;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
@@ -52,7 +52,7 @@ public class StayedLockedLoansTasklet implements Tasklet {
private LoanAccountsStayedLockedData buildLoanAccountData() {
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
List<LoanIdAndExternalIdAndAccountNo> stayedLockedLoanAccounts = retrieveLoanIdService
List<COBIdAndExternalIdAndAccountNo> stayedLockedLoanAccounts = retrieveLoanIdService
.findAllStayedLockedByCobBusinessDate(cobBusinessDate);
List<LoanAccountStayedLockedData> loanAccounts = new ArrayList<>();
stayedLockedLoanAccounts.forEach(loanAccount -> {
@@ -27,7 +27,7 @@ import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.loan.LoanCOBConstant;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -70,7 +70,7 @@ public class AsyncLoanCOBExecutorServiceImpl implements AsyncLoanCOBExecutorServ
try {
ThreadLocalContextUtil.init(context);
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDate = retrieveLoanIdService
List<COBIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDate = retrieveLoanIdService
.retrieveLoanIdsOldestCobProcessed(cobBusinessDate);
LocalDate oldestCOBProcessedDate = !loanIdAndLastClosedBusinessDate.isEmpty()
@@ -37,11 +37,11 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException;
import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException;
import org.apache.fineract.cob.loan.LoanCOBConstant;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -101,7 +101,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public CommandProcessingResult executeInlineJob(JsonCommand command, String jobName) throws LoanAccountLockCannotBeOverruledException {
public CommandProcessingResult executeInlineJob(JsonCommand command, String jobName) throws AccountLockCannotBeOverruledException {
List<Long> loanIds = dataParser.parseExecution(command);
validateLoanIdsListSize(loanIds);
execute(loanIds, jobName);
@@ -111,7 +111,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
@Override
public void execute(List<Long> loanIds, String jobName) {
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
List<LoanIdAndLastClosedBusinessDate> loansToBeProcessed = getLoansToBeProcessed(loanIds, cobBusinessDate);
List<COBIdAndLastClosedBusinessDate> loansToBeProcessed = getLoansToBeProcessed(loanIds, cobBusinessDate);
LocalDate executingBusinessDate = getOldestCOBBusinessDate(loansToBeProcessed).plusDays(1);
if (!loansToBeProcessed.isEmpty()) {
while (!DateUtils.isAfter(executingBusinessDate, cobBusinessDate)) {
@@ -121,7 +121,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
}
}
private List<Long> getLoanIdsToBeProcessed(List<LoanIdAndLastClosedBusinessDate> loansToBeProcessed, LocalDate executingBusinessDate) {
private List<Long> getLoanIdsToBeProcessed(List<COBIdAndLastClosedBusinessDate> loansToBeProcessed, LocalDate executingBusinessDate) {
List<Long> loanIdsToBeProcessed = new ArrayList<>();
loansToBeProcessed.forEach(loan -> {
if (loan.getLastClosedBusinessDate() != null) {
@@ -159,16 +159,16 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
}
}
private LocalDate getOldestCOBBusinessDate(List<LoanIdAndLastClosedBusinessDate> loans) {
LoanIdAndLastClosedBusinessDate oldestLoan = loans.stream().min(Comparator
.comparing(LoanIdAndLastClosedBusinessDate::getLastClosedBusinessDate, Comparator.nullsLast(Comparator.naturalOrder())))
private LocalDate getOldestCOBBusinessDate(List<COBIdAndLastClosedBusinessDate> loans) {
COBIdAndLastClosedBusinessDate oldestLoan = loans.stream().min(Comparator
.comparing(COBIdAndLastClosedBusinessDate::getLastClosedBusinessDate, Comparator.nullsLast(Comparator.naturalOrder())))
.orElse(null);
return oldestLoan != null && oldestLoan.getLastClosedBusinessDate() != null ? oldestLoan.getLastClosedBusinessDate()
: ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(1);
}
private List<LoanIdAndLastClosedBusinessDate> getLoansToBeProcessed(List<Long> loanIds, LocalDate cobBusinessDate) {
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates = new ArrayList<>();
private List<COBIdAndLastClosedBusinessDate> getLoansToBeProcessed(List<Long> loanIds, LocalDate cobBusinessDate) {
List<COBIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates = new ArrayList<>();
List<List<Long>> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit());
partitions.forEach(partition -> loanIdAndLastClosedBusinessDates
.addAll(retrieveLoanIdService.retrieveLoanIdsBehindDateOrNull(cobBusinessDate, partition)));
@@ -194,7 +194,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
if (!alreadyLockedLoanIds.isEmpty()) {
String message = "There is a hard lock on the loan account without any error, so it can't be overruled.";
String loanIdsMessage = " Locked loan IDs: " + alreadyLockedLoanIds;
throw new LoanAccountLockCannotBeOverruledException(message + loanIdsMessage);
throw new AccountLockCannotBeOverruledException(message + loanIdsMessage);
}
return loanAccountLocks;
@@ -229,7 +229,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
loanAccountLockRepository.saveAndFlush(loanAccountLock);
} catch (Exception e) {
log.error("Error updating lock on loan account. Locked loan ID: {}", loanAccountLock.getLoanId(), e);
throw new LoanAccountLockCannotBeOverruledException(
throw new AccountLockCannotBeOverruledException(
"Error updating lock on loan account. Locked loan ID: %s".formatted(loanAccountLock.getLoanId()), e);
}
});
@@ -24,7 +24,7 @@ import org.apache.fineract.cob.loan.LoanCOBBusinessStep;
import org.springframework.stereotype.Service;
@Service
public class BusinessStepCategoryServiceImpl implements BusinessStepCategoryService {
public class LoanBusinessStepCategoryServiceImpl implements BusinessStepCategoryService {
private static final Map<BusinessStepCategory, Class<? extends COBBusinessStep>> businessSteps = Map.of(BusinessStepCategory.LOAN,
LoanCOBBusinessStep.class);
@@ -22,8 +22,8 @@ import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.IsCatchUpRunningDTO;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
import org.apache.fineract.cob.loan.LoanCOBConstant;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
@@ -51,13 +51,13 @@ public class LoanCOBCatchUpServiceImpl implements LoanCOBCatchUpService {
@Override
public OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan() {
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDate = retrieveLoanIdService
List<COBIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDate = retrieveLoanIdService
.retrieveLoanIdsOldestCobProcessed(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE));
OldestCOBProcessedLoanDTO oldestCOBProcessedLoanDTO = new OldestCOBProcessedLoanDTO();
oldestCOBProcessedLoanDTO.setLoanIds(loanIdAndLastClosedBusinessDate.stream().map(LoanIdAndLastClosedBusinessDate::getId).toList());
oldestCOBProcessedLoanDTO.setCobProcessedDate(
loanIdAndLastClosedBusinessDate.stream().map(LoanIdAndLastClosedBusinessDate::getLastClosedBusinessDate).findFirst()
.orElse(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)));
oldestCOBProcessedLoanDTO.setLoanIds(loanIdAndLastClosedBusinessDate.stream().map(COBIdAndLastClosedBusinessDate::getId).toList());
oldestCOBProcessedLoanDTO
.setCobProcessedDate(loanIdAndLastClosedBusinessDate.stream().map(COBIdAndLastClosedBusinessDate::getLastClosedBusinessDate)
.findFirst().orElse(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)));
oldestCOBProcessedLoanDTO.setCobBusinessDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE));
return oldestCOBProcessedLoanDTO;
}
@@ -71,8 +71,8 @@ public class LoanCOBCatchUpServiceImpl implements LoanCOBCatchUpService {
@Override
public IsCatchUpRunningDTO isCatchUpRunning() {
LocalDate runningCatchUpBusinessDate = jobExecutionRepository.getBusinessDateOfRunningJobByExecutionParameter(
LoanCOBConstant.JOB_NAME, LoanCOBConstant.LOAN_COB_CUSTOM_JOB_PARAMETER_KEY, LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME,
"true", LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
LoanCOBConstant.JOB_NAME, LoanCOBConstant.COB_CUSTOM_JOB_PARAMETER_KEY, LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME, "true",
LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
return new IsCatchUpRunningDTO(runningCatchUpBusinessDate != null, runningCatchUpBusinessDate);
}
}
@@ -39,7 +39,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.batch.domain.BatchRequest;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
import org.apache.fineract.cob.service.LoanAccountLockService;
@@ -195,7 +195,7 @@ public class LoanCOBFilterHelper implements InitializingBean {
}
public boolean isLoanBehind(List<Long> loanIds) {
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates = new ArrayList<>();
List<COBIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates = new ArrayList<>();
List<List<Long>> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit());
partitions.forEach(partition -> loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService
.retrieveLoanIdsBehindDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)));
@@ -52,7 +52,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException;
import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException;
import org.apache.fineract.cob.service.LoanAccountLockService;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
@@ -1904,7 +1904,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
if (!lockedLoanIds.isEmpty()) {
throw new LoanAccountLockCannotBeOverruledException("There are hard-lcoked loan accounts: " + lockedLoanIds);
throw new AccountLockCannotBeOverruledException("There are hard-lcoked loan accounts: " + lockedLoanIds);
}
this.loanRepositoryWrapper.flush();
@@ -34,7 +34,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.cob.exceptions.LoanLockCannotBeAppliedException;
@@ -76,7 +76,7 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
ThreadLocalContextUtil.setBusinessDates(businessDateMap);
StepExecution stepExecution = new StepExecution("test", null);
ExecutionContext executionContext = new ExecutionContext();
LoanCOBParameter loanCOBParameter = new LoanCOBParameter(1L, 4L);
COBParameter loanCOBParameter = new COBParameter(1L, 4L);
executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, loanCOBParameter);
lenient().when(
retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false))
@@ -28,8 +28,8 @@ import java.util.Map;
import java.util.Set;
import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.data.BusinessStepNameAndOrder;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.data.COBPartition;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.infrastructure.springbatch.PropertyService;
import org.junit.jupiter.api.Assertions;
@@ -68,7 +68,7 @@ class LoanCOBPartitionerTest {
when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
.thenReturn(BUSINESS_STEP_SET);
when(retrieveLoanIdService.retrieveLoanCOBPartitions(1L, BUSINESS_DATE, false, 5))
.thenReturn(List.of(new LoanCOBPartition(1L,10L, 1L, 5L), new LoanCOBPartition(11L,20L, 2L, 4L)));
.thenReturn(List.of(new COBPartition(1L,10L, 1L, 5L), new COBPartition(11L,20L, 2L, 4L)));
LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveLoanIdService, jobOperator, jobExplorer, 1L);
loanCOBPartitioner.setBusinessDate(BUSINESS_DATE);
loanCOBPartitioner.setIsCatchUp(false);
@@ -128,7 +128,7 @@ class LoanCOBPartitionerTest {
private void validatePartitions(Map<String, ExecutionContext> partitions, int index, long min, long max) {
Assertions.assertEquals(BUSINESS_STEP_SET,
partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get(LoanCOBConstant.BUSINESS_STEPS));
Assertions.assertEquals(new LoanCOBParameter(min, max),
Assertions.assertEquals(new COBParameter(min, max),
partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get(LoanCOBConstant.LOAN_COB_PARAMETER));
Assertions.assertEquals("partition_" + index, partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get("partition"));
}
@@ -36,7 +36,7 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.cob.exceptions.LoanReadException;
@@ -82,7 +82,7 @@ public class LoanItemReaderStepDefinitions implements En {
minLoanId = splitAccounts.get(0);
maxLoanId = splitAccounts.get(splitAccounts.size() - 1);
}
LoanCOBParameter loanCOBParameter = new LoanCOBParameter(minLoanId, maxLoanId);
COBParameter loanCOBParameter = new COBParameter(minLoanId, maxLoanId);
stepExecutionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, loanCOBParameter);
stepExecution.setExecutionContext(stepExecutionContext);
@@ -33,7 +33,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.COBParameter;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
@@ -86,7 +86,7 @@ class LoanItemReaderTest {
LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, customJobParameterResolver,
loanLockingService);
when(stepExecution.getExecutionContext()).thenReturn(executionContext);
LoanCOBParameter loanCOBParameter = new LoanCOBParameter(1L, 5L);
COBParameter loanCOBParameter = new COBParameter(1L, 5L);
when(executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER)).thenReturn(loanCOBParameter);
when(retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false))
.thenReturn(new ArrayList<>(List.of(1L, 2L, 3L, 4L, 5L)));
@@ -114,7 +114,7 @@ class LoanItemReaderTest {
LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, customJobParameterResolver,
loanLockingService);
when(stepExecution.getExecutionContext()).thenReturn(executionContext);
LoanCOBParameter loanCOBParameter = new LoanCOBParameter(1L, 5L);
COBParameter loanCOBParameter = new COBParameter(1L, 5L);
when(executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER)).thenReturn(loanCOBParameter);
when(retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false))
.thenReturn(new ArrayList<>(List.of()));
@@ -135,7 +135,7 @@ class LoanItemReaderTest {
LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, customJobParameterResolver,
loanLockingService);
when(stepExecution.getExecutionContext()).thenReturn(executionContext);
LoanCOBParameter loanCOBParameter = new LoanCOBParameter(1L, 100L);
COBParameter loanCOBParameter = new COBParameter(1L, 100L);
when(executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER)).thenReturn(loanCOBParameter);
when(retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false))
.thenReturn(new ArrayList<>(IntStream.rangeClosed(1, 100).boxed().map(Long::valueOf).toList()));
@@ -22,7 +22,7 @@ import static org.mockito.Mockito.times;
import java.time.LocalDate;
import java.util.List;
import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.cob.data.COBPartition;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -48,7 +48,7 @@ public class RetrieveAllNonClosedLoanIdServiceImplTest {
@Captor
private ArgumentCaptor<SqlParameterSource> paramsCaptor;
@Captor
private ArgumentCaptor<RowMapper<LoanCOBPartition>> rowMapper;
private ArgumentCaptor<RowMapper<COBPartition>> rowMapper;
@Test
public void testRetrieveLoanCOBPartitionsNoCatchup() {
@@ -34,8 +34,8 @@ import java.time.LocalDate;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -82,7 +82,7 @@ class InlineLoanCOBExecutorServiceImplTest {
@Test
void shouldExceptionThrownIfLoanIsAlreadyLocked() {
JsonCommand command = mock(JsonCommand.class);
LoanIdAndLastClosedBusinessDate loan = mock(LoanIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate loan = mock(COBIdAndLastClosedBusinessDate.class);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
HashMap<BusinessDateType, LocalDate> businessDates = new HashMap<>();
LocalDate businessDate = LocalDate.now(ZoneId.systemDefault());
@@ -90,7 +90,7 @@ class InlineLoanCOBExecutorServiceImplTest {
businessDates.put(BusinessDateType.COB_DATE, businessDate.minusDays(1));
ThreadLocalContextUtil.setBusinessDates(businessDates);
when(transactionTemplate.execute(any())).thenThrow(new LoanAccountLockCannotBeOverruledException(""));
when(transactionTemplate.execute(any())).thenThrow(new AccountLockCannotBeOverruledException(""));
when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
when(fineractProperties.getApi()).thenReturn(fineractApiProperties);
when(dataParser.parseExecution(any())).thenReturn(List.of(1L));
@@ -98,15 +98,15 @@ class InlineLoanCOBExecutorServiceImplTest {
when(fineractApiProperties.getBodyItemSizeLimit()).thenReturn(fineractBodyItemSizeLimitProperties);
when(fineractBodyItemSizeLimitProperties.getInlineLoanCob()).thenReturn(1000);
when(retrieveLoanIdService.retrieveLoanIdsBehindDateOrNull(any(), anyList())).thenReturn(List.of(loan));
assertThrows(LoanAccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB"));
assertThrows(AccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB"));
}
@Test
void shouldListBePartitioned() {
JsonCommand command = mock(JsonCommand.class);
LoanIdAndLastClosedBusinessDate loan1 = mock(LoanIdAndLastClosedBusinessDate.class);
LoanIdAndLastClosedBusinessDate loan2 = mock(LoanIdAndLastClosedBusinessDate.class);
LoanIdAndLastClosedBusinessDate loan3 = mock(LoanIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate loan1 = mock(COBIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate loan2 = mock(COBIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate loan3 = mock(COBIdAndLastClosedBusinessDate.class);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
HashMap<BusinessDateType, LocalDate> businessDates = new HashMap<>();
LocalDate businessDate = LocalDate.now(ZoneId.systemDefault());
@@ -114,7 +114,7 @@ class InlineLoanCOBExecutorServiceImplTest {
businessDates.put(BusinessDateType.COB_DATE, businessDate.minusDays(1));
ThreadLocalContextUtil.setBusinessDates(businessDates);
when(transactionTemplate.execute(any())).thenThrow(new LoanAccountLockCannotBeOverruledException(""));
when(transactionTemplate.execute(any())).thenThrow(new AccountLockCannotBeOverruledException(""));
when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
when(fineractProperties.getApi()).thenReturn(fineractApiProperties);
when(dataParser.parseExecution(any())).thenReturn(List.of(1L, 2L, 3L));
@@ -122,16 +122,16 @@ class InlineLoanCOBExecutorServiceImplTest {
when(fineractApiProperties.getBodyItemSizeLimit()).thenReturn(fineractBodyItemSizeLimitProperties);
when(fineractBodyItemSizeLimitProperties.getInlineLoanCob()).thenReturn(1000);
when(retrieveLoanIdService.retrieveLoanIdsBehindDateOrNull(any(), anyList())).thenReturn(List.of(loan1, loan2, loan3));
assertThrows(LoanAccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB"));
assertThrows(AccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB"));
verify(retrieveLoanIdService, times(2)).retrieveLoanIdsBehindDateOrNull(any(), anyList());
}
@Test
void shouldOldestCloseBusinessDateReturnWithCorrectDate()
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
LoanIdAndLastClosedBusinessDate loan1 = mock(LoanIdAndLastClosedBusinessDate.class);
LoanIdAndLastClosedBusinessDate loan2 = mock(LoanIdAndLastClosedBusinessDate.class);
LoanIdAndLastClosedBusinessDate loan3 = mock(LoanIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate loan1 = mock(COBIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate loan2 = mock(COBIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate loan3 = mock(COBIdAndLastClosedBusinessDate.class);
when(loan1.getLastClosedBusinessDate()).thenReturn(null);
when(loan2.getLastClosedBusinessDate()).thenReturn(LocalDate.of(2023, 1, 10));
when(loan3.getLastClosedBusinessDate()).thenReturn(LocalDate.of(2023, 1, 11));
@@ -48,7 +48,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
import org.apache.fineract.cob.service.LoanAccountLockService;
@@ -312,7 +312,7 @@ class LoanCOBApiFilterTest {
businessDates.put(BusinessDateType.COB_DATE, businessDate.minusDays(1));
ThreadLocalContextUtil.setBusinessDates(businessDates);
LoanIdAndLastClosedBusinessDate result = mock(LoanIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate result = mock(COBIdAndLastClosedBusinessDate.class);
given(result.getId()).willReturn(2L);
given(result.getLastClosedBusinessDate()).willReturn(businessDate.minusDays(2));
given(request.getPathInfo()).willReturn("/v1/loans/2?command=approve");
@@ -346,7 +346,7 @@ class LoanCOBApiFilterTest {
businessDates.put(BusinessDateType.COB_DATE, businessDate.minusDays(1));
ThreadLocalContextUtil.setBusinessDates(businessDates);
LoanIdAndLastClosedBusinessDate result = mock(LoanIdAndLastClosedBusinessDate.class);
COBIdAndLastClosedBusinessDate result = mock(COBIdAndLastClosedBusinessDate.class);
given(result.getId()).willReturn(2L);
given(request.getPathInfo()).willReturn("/v1/loans/2?command=approve");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
+1
View File
@@ -25,6 +25,7 @@ dependencies {
// implementation dependencies are directly used (compiled against) in src/main (and src/test)
//
implementation(project(path: ':fineract-core'))
implementation(project(path: ':fineract-cob'))
implementation(project(path: ':fineract-accounting'))
implementation(project(path: ':fineract-charge'))
implementation(project(path: ':fineract-rates'))
+1
View File
@@ -48,6 +48,7 @@ buildCache {
rootProject.name='fineract'
include ':fineract-core'
include ':fineract-cob'
include ':fineract-validation'
include ':fineract-command'
include ':fineract-accounting'