You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/09/07 14:30:28 UTC

[fineract] branch develop updated (90896a646 -> 17454d0e2)

This is an automated email from the ASF dual-hosted git repository.

arnold pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


    from 90896a646 FINERACT-1694: Storing event schema along with external events
     new f26e14ae0 Delinquency classification as a Loan COB business step
     new 17454d0e2 Delinquency classification as a Loan COB business step

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../fineract/cob/loan/InitialisationTasklet.java   |  62 ++++++++++++
 .../cob/loan/LoanCOBWorkerConfiguration.java       |  30 +++++-
 ...ava => SetLoanDelinquencyTagsBusinessStep.java} |  24 +++--
 .../service/DelinquencyWritePlatformService.java   |   2 -
 .../DelinquencyWritePlatformServiceImpl.java       |  11 ---
 .../portfolio/loanaccount/domain/Loan.java         |   3 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...47_add_loan_delinquency_tags_business_step.xml} |  12 +--
 .../BusinessConfigurationApiTest.java              |   7 +-
 .../DelinquencyBucketsIntegrationTest.java         | 105 ++++++++++++++++++++-
 10 files changed, 216 insertions(+), 41 deletions(-)
 create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/InitialisationTasklet.java
 copy fineract-provider/src/main/java/org/apache/fineract/cob/loan/{ApplyChargeToOverdueLoansBusinessStep.java => SetLoanDelinquencyTagsBusinessStep.java} (58%)
 copy fineract-provider/src/main/resources/db/changelog/tenant/parts/{0008_loan_charge_add_external_id.xml => 0047_add_loan_delinquency_tags_business_step.xml} (79%)


[fineract] 02/02: Delinquency classification as a Loan COB business step

Posted by ar...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 17454d0e206996650e147bbafe1fcf31beb61dbe
Author: Jose Alberto Hernandez <al...@MacBook-Pro.local>
AuthorDate: Wed Sep 7 07:59:43 2022 -0500

    Delinquency classification as a Loan COB business step
---
 .../java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
index efe17a38f..e816f7cfe 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
@@ -82,7 +82,7 @@ public class LoanCOBWorkerConfiguration {
 
     @Bean
     public Flow flow() {
-        return new FlowBuilder<Flow>("cobFlow").start(initialisationStep()).next(loanBusinessStep()).next(loanBusinessStep()).build();
+        return new FlowBuilder<Flow>("cobFlow").start(initialisationStep()).next(loanBusinessStep()).build();
     }
 
     @Bean


[fineract] 01/02: Delinquency classification as a Loan COB business step

Posted by ar...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit f26e14ae0718f81f076ae0ccea69a54835a48d49
Author: Jose Alberto Hernandez <al...@MacBook-Pro.local>
AuthorDate: Mon Sep 5 16:33:29 2022 -0500

    Delinquency classification as a Loan COB business step
---
 .../fineract/cob/loan/InitialisationTasklet.java   |  62 ++++++++++++
 .../cob/loan/LoanCOBWorkerConfiguration.java       |  30 +++++-
 .../loan/SetLoanDelinquencyTagsBusinessStep.java   |  52 ++++++++++
 .../service/DelinquencyWritePlatformService.java   |   2 -
 .../DelinquencyWritePlatformServiceImpl.java       |  11 ---
 .../portfolio/loanaccount/domain/Loan.java         |   3 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...047_add_loan_delinquency_tags_business_step.xml |  32 +++++++
 .../BusinessConfigurationApiTest.java              |   7 +-
 .../DelinquencyBucketsIntegrationTest.java         | 105 ++++++++++++++++++++-
 10 files changed, 283 insertions(+), 22 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InitialisationTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InitialisationTasklet.java
new file mode 100644
index 000000000..f352c17b8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InitialisationTasklet.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.cob.loan;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.batch.core.ExitStatus;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.repeat.RepeatStatus;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+@Slf4j
+@RequiredArgsConstructor
+public class InitialisationTasklet implements Tasklet, StepExecutionListener {
+
+    private final AppUserRepositoryWrapper userRepository;
+    private StepExecution stepExecution;
+
+    @Override
+    public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
+        AppUser user = userRepository.fetchSystemUser();
+        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, user.getPassword(),
+                new NullAuthoritiesMapper().mapAuthorities(user.getAuthorities()));
+        SecurityContextHolder.getContext().setAuthentication(auth);
+        return RepeatStatus.FINISHED;
+    }
+
+    @Override
+    public void beforeStep(@NotNull StepExecution stepExecution) {
+        this.stepExecution = stepExecution;
+    }
+
+    @Override
+    public ExitStatus afterStep(@NotNull StepExecution stepExecution) {
+        return ExitStatus.COMPLETED;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
index ddee35820..efe17a38f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
@@ -25,10 +25,13 @@ import org.apache.fineract.cob.COBPropertyService;
 import org.apache.fineract.infrastructure.jobs.service.JobName;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
 import org.springframework.batch.core.Job;
 import org.springframework.batch.core.Step;
 import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
 import org.springframework.batch.core.configuration.annotation.StepScope;
+import org.springframework.batch.core.job.builder.FlowBuilder;
+import org.springframework.batch.core.job.flow.Flow;
 import org.springframework.batch.core.launch.support.RunIdIncrementer;
 import org.springframework.batch.integration.partition.RemotePartitioningWorkerStepBuilderFactory;
 import org.springframework.batch.item.ItemProcessor;
@@ -57,19 +60,42 @@ public class LoanCOBWorkerConfiguration {
     private QueueChannel inboundRequests;
     @Autowired
     private COBBusinessStepService cobBusinessStepService;
+    @Autowired
+    private AppUserRepositoryWrapper userRepository;
 
-    @Bean(name = "Loan COB worker")
-    public Step loanCOBWorkerStep() {
+    @Bean
+    public Step loanBusinessStep() {
         return stepBuilderFactory.get("Loan COB worker").inputChannel(inboundRequests)
                 .<Loan, Loan>chunk(cobPropertyService.getChunkSize(JobName.LOAN_COB.name())).reader(itemReader(null))
                 .processor(itemProcessor()).writer(itemWriter()).build();
     }
 
+    @Bean(name = "Loan COB worker")
+    public Step loanCOBWorkerStep() {
+        return stepBuilderFactory.get("Loan COB worker - Step").inputChannel(inboundRequests).flow(flow()).build();
+    }
+
     @Bean
     public Job loanCOBWorkerJob() {
         return jobBuilderFactory.get("Loan COB worker").start(loanCOBWorkerStep()).incrementer(new RunIdIncrementer()).build();
     }
 
+    @Bean
+    public Flow flow() {
+        return new FlowBuilder<Flow>("cobFlow").start(initialisationStep()).next(loanBusinessStep()).next(loanBusinessStep()).build();
+    }
+
+    @Bean
+    public Step initialisationStep() {
+        return stepBuilderFactory.get("Initalisation - Step").inputChannel(inboundRequests).tasklet(initialiseContext()).build();
+    }
+
+    @Bean
+    @StepScope
+    public InitialisationTasklet initialiseContext() {
+        return new InitialisationTasklet(userRepository);
+    }
+
     @Bean
     @StepScope
     public ItemReader<Loan> itemReader(@Value("#{stepExecutionContext['loanIds']}") List<Integer> data) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStep.java
new file mode 100644
index 000000000..d0a1168e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStep.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.cob.loan;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SetLoanDelinquencyTagsBusinessStep implements LoanCOBBusinessStep {
+
+    private final LoanAccountDomainService loanAccountDomainService;
+
+    @Override
+    public Loan execute(Loan loan) {
+        log.debug("Set Loan Delinquency Tags Business Step {}", loan.getId());
+        loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
+        return loan;
+    }
+
+    @Override
+    public String getEnumStyledName() {
+        return "LOAN_DELINQUENCY_CLASSIFICATION";
+    }
+
+    @Override
+    public String getHumanReadableName() {
+        return "Loan Delinquency Classification";
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformService.java
index b2ee3bac2..8795ace83 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformService.java
@@ -30,8 +30,6 @@ public interface DelinquencyWritePlatformService {
 
     CommandProcessingResult deleteDelinquencyRange(Long delinquencyRangeId, JsonCommand command);
 
-    CommandProcessingResult setLoanDelinquencyTag(Long loanId, Long delinquencyRangeId, JsonCommand command);
-
     CommandProcessingResult createDelinquencyBucket(JsonCommand command);
 
     CommandProcessingResult updateDelinquencyBucket(Long delinquencyBucketId, JsonCommand command);
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 45fa65f63..4d1dc384c 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
@@ -104,12 +104,6 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
         return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
     }
 
-    @Override
-    public CommandProcessingResult setLoanDelinquencyTag(Long loanId, Long delinquencyRangeId, JsonCommand command) {
-        setLoanDelinquencyTag(loanId, delinquencyRangeId);
-        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loanId).build();
-    }
-
     @Override
     public CommandProcessingResult createDelinquencyBucket(JsonCommand command) {
         DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
@@ -328,11 +322,6 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
         return changes;
     }
 
-    public Map<String, Object> setLoanDelinquencyTag(Long loanId, Long delinquencyRangeId) {
-        final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
-        return setLoanDelinquencyTag(loan, delinquencyRangeId);
-    }
-
     public Map<String, Object> setLoanDelinquencyTag(Loan loan, Long delinquencyRangeId) {
         Map<String, Object> changes = new HashMap<>();
         List<LoanDelinquencyTagHistory> loanDelinquencyTagHistory = new ArrayList<>();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index e5bbb38a7..95088b15e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -27,7 +27,6 @@ import java.math.BigDecimal;
 import java.math.MathContext;
 import java.math.RoundingMode;
 import java.time.LocalDate;
-import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
@@ -3770,7 +3769,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
                 throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate);
             }
 
-            LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
             loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId);
             LocalDate lastTransactionDate = getLastUserTransactionDate();
             if (lastTransactionDate.isAfter(writtenOffOnLocalDate)) {
@@ -6877,4 +6875,5 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
         }
         return ageOfOverdueDays;
     }
+
 }
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 73aee0f7c..d79f96d5c 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -66,4 +66,5 @@
     <include file="parts/0044_table_report_query_fix.xml" relativeToChangelogFile="true"/>
     <include file="parts/0045_external_event_table_data_binary.xml" relativeToChangelogFile="true"/>
     <include file="parts/0046_external_event_table_schema_info.xml" relativeToChangelogFile="true"/>
+    <include file="parts/0047_add_loan_delinquency_tags_business_step.xml" relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0047_add_loan_delinquency_tags_business_step.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0047_add_loan_delinquency_tags_business_step.xml
new file mode 100644
index 000000000..aa8416636
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0047_add_loan_delinquency_tags_business_step.xml
@@ -0,0 +1,32 @@
+<?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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+    <changeSet id="1" author="fineract">
+        <insert tableName="m_batch_business_steps">
+            <column name="job_name" value="LOAN_CLOSE_OF_BUSINESS"/>
+            <column name="step_name" value="LOAN_DELINQUENCY_CLASSIFICATION"/>
+            <column name="step_order" value="2"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BusinessConfigurationApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BusinessConfigurationApiTest.java
index 0e10b2253..8f1031396 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BusinessConfigurationApiTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BusinessConfigurationApiTest.java
@@ -41,9 +41,10 @@ public class BusinessConfigurationApiTest {
 
     private ResponseSpecification responseSpec;
     private RequestSpecification requestSpec;
-    private static final String LOAN_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS";
-    private static final String LOAN_CATEGORY_NAME = "loan";
-    private static final String APPLY_CHARGE_TO_OVERDUE_LOANS = "APPLY_CHARGE_TO_OVERDUE_LOANS";
+    public static final String LOAN_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS";
+    public static final String LOAN_CATEGORY_NAME = "loan";
+    public static final String APPLY_CHARGE_TO_OVERDUE_LOANS = "APPLY_CHARGE_TO_OVERDUE_LOANS";
+    public static final String LOAN_DELINQUENCY_CLASSIFICATION = "LOAN_DELINQUENCY_CLASSIFICATION";
 
     @BeforeEach
     public void setup() {
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
index 242156523..1efd1d9af 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import io.restassured.builder.RequestSpecBuilder;
 import io.restassured.builder.ResponseSpecBuilder;
@@ -46,8 +47,10 @@ import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
 import org.apache.fineract.client.models.PutDelinquencyBucketResponse;
 import org.apache.fineract.client.models.PutDelinquencyRangeResponse;
+import org.apache.fineract.cob.data.JobBusinessStepConfigData;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.BusinessStepConfigurationHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
@@ -237,7 +240,6 @@ public class DelinquencyBucketsIntegrationTest {
         log.info("Current date {}", businessDate);
         BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
 
-        // Given
         final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
         final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
 
@@ -279,7 +281,6 @@ public class DelinquencyBucketsIntegrationTest {
         final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
                 getLoanProductsProductResponse.getId().toString(), operationDate);
 
-        // When
         // Run first time the Job
         final String jobName = "Loan Delinquency Classification";
         schedulerJobHelper.executeAndAwaitJob(jobName);
@@ -320,6 +321,106 @@ public class DelinquencyBucketsIntegrationTest {
         GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
     }
 
+    @Test
+    public void testLoanClassificationStepAsPartOfCOB() {
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+
+        LocalDate businessDate = Utils.getLocalDateOfTenant();
+        businessDate = businessDate.minusDays(57);
+        log.info("Current date {}", businessDate);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+
+        // Given
+        final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
+
+        ArrayList<Integer> rangeIds = new ArrayList<>();
+        String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
+        PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
+                jsonRange);
+        rangeIds.add(delinquencyRangeResponse.getResourceId());
+        jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60);
+        // Create
+        delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
+        rangeIds.add(delinquencyRangeResponse.getResourceId());
+
+        final GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
+                delinquencyRangeResponse.getResourceId());
+        final String classificationExpected = range.getClassification();
+        log.info("Expected Delinquency Range classification {}", classificationExpected);
+
+        String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
+        PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
+                responseSpec, jsonBucket);
+        final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+                delinquencyBucketResponse.getResourceId());
+
+        // Client and Loan account creation
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+        final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
+                delinquencyBucket.getId());
+        assertNotNull(getLoanProductsProductResponse);
+        log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
+        assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
+
+        final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+        // Older date to have more than one overdue installment
+        final LocalDate transactionDate = todaysDate.minusDays(65);
+        String operationDate = Utils.dateFormatter.format(transactionDate);
+
+        // Create Loan Account
+        final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
+                getLoanProductsProductResponse.getId().toString(), operationDate);
+
+        // COB Step Validation
+        final JobBusinessStepConfigData jobBusinessStepConfigData = BusinessStepConfigurationHelper
+                .getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, BusinessConfigurationApiTest.LOAN_JOB_NAME);
+        assertNotNull(jobBusinessStepConfigData);
+        assertEquals(BusinessConfigurationApiTest.LOAN_JOB_NAME, jobBusinessStepConfigData.getJobName());
+        assertTrue(jobBusinessStepConfigData.getBusinessSteps().size() > 0);
+        assertTrue(jobBusinessStepConfigData.getBusinessSteps().stream()
+                .anyMatch(businessStep -> BusinessConfigurationApiTest.LOAN_DELINQUENCY_CLASSIFICATION.equals(businessStep.getStepName())));
+
+        // Run first time the Loan COB Job
+        final String jobName = "Loan COB";
+        schedulerJobHelper.executeAndAwaitJob(jobName);
+
+        // Get loan details expecting to have not a delinquency classification
+        GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+        final GetDelinquencyRangesResponse firstTestCase = getLoansLoanIdResponse.getDelinquencyRange();
+        log.info("Loan Delinquency Range is null {}", (firstTestCase == null));
+        GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule = getLoansLoanIdResponse.getRepaymentSchedule();
+        if (getLoanRepaymentSchedule != null) {
+            log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size());
+            for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) {
+                log.info("Period number {} for due date {} and outstanding {}", period.getPeriod(), period.getDueDate(),
+                        period.getTotalOutstandingForPeriod());
+            }
+        }
+
+        // Move the Business date to get older the loan and to have an overdue loan
+        businessDate = businessDate.plusDays(40);
+        log.info("Current date {}", businessDate);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
+        // Run Second time the Job
+        schedulerJobHelper.executeAndAwaitJob(jobName);
+
+        // Get loan details expecting to have a delinquency classification
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+        final GetDelinquencyRangesResponse secondTestCase = getLoansLoanIdResponse.getDelinquencyRange();
+        log.info("Loan Delinquency Range is {}", secondTestCase.getClassification());
+
+        // Then
+        assertNotNull(delinquencyBucketResponse);
+        assertNotNull(getLoanProductsProductResponse);
+        assertNull(firstTestCase);
+        assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
+        assertNotNull(secondTestCase);
+        assertEquals(secondTestCase.getClassification(), classificationExpected);
+
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+    }
+
     @Test
     public void testLoanClassificationRealtime() {
         // Given