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 2023/04/21 12:20:10 UTC
[fineract] branch develop updated: FINERACT-1724: Add final accrual when loan is overpaid
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
The following commit(s) were added to refs/heads/develop by this push:
new cefcb5342 FINERACT-1724: Add final accrual when loan is overpaid
cefcb5342 is described below
commit cefcb5342498215419a1640f498a0483f1d780a3
Author: Adam Saghy <ad...@gmail.com>
AuthorDate: Thu Apr 20 19:17:56 2023 +0200
FINERACT-1724: Add final accrual when loan is overpaid
---
.../portfolio/loanaccount/domain/Loan.java | 7 +-
.../domain/LoanAccountDomainService.java | 2 +-
.../domain/LoanAccountDomainServiceJpa.java | 14 +++-
.../LoanStatusChangePlatformServiceImpl.java | 4 +-
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 27 ++++----
...DueDateRespectiveLoanRepaymentScheduleTest.java | 30 ++++++---
.../LoanAccountOverpaidDateStatusTest.java | 13 +---
.../LoanChargeSpecificDueDateTest.java | 78 ++++++++++++++++++++++
8 files changed, 131 insertions(+), 44 deletions(-)
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 e56edebdf..6b030c295 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
@@ -3423,7 +3423,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
boolean isOverpaid = getTotalOverpaid() != null && getTotalOverpaid().compareTo(BigDecimal.ZERO) > 0;
if (isOverpaid) {
// FIXME - kw - update account balance to negative amount.
- handleLoanOverpayment(loanLifecycleStateMachine);
+ handleLoanOverpayment(transactionDate, loanLifecycleStateMachine);
statusChanged = true;
} else if (this.summary.isRepaidInFull(loanCurrency())) {
handleLoanRepaymentInFull(transactionDate, loanLifecycleStateMachine);
@@ -3536,10 +3536,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
incomeDetailsMap.put(PENALTY, penalty);
}
- private void handleLoanOverpayment(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
-
+ private void handleLoanOverpayment(LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+ this.overpaidOnDate = transactionDate;
loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, this);
- this.overpaidOnDate = DateUtils.getBusinessLocalDate();
this.closedOnDate = null;
this.actualMaturityDate = null;
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
index 1eb1bb464..976597686 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
@@ -99,5 +99,5 @@ public interface LoanAccountDomainService {
LoanTransaction creditBalanceRefund(Loan loan, LocalDate transactionDate, BigDecimal transactionAmount, String noteText,
ExternalId externalId, PaymentDetail paymentDetail);
- void applyIncomeAccrualTransaction(Loan loan);
+ void applyFinalIncomeAccrualTransaction(Loan loan);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 6a0b36d43..b7a6e3dc8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -877,7 +877,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
@Override
- public void applyIncomeAccrualTransaction(Loan loan) {
+ public void applyFinalIncomeAccrualTransaction(Loan loan) {
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()
// to avoid collision with processIncomeAccrualTransactionOnLoanClosure()
&& !(loan.getLoanInterestRecalculationDetails() != null
@@ -904,7 +904,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
if (total.isGreaterThanZero()) {
ExternalId externalId = externalIdFactory.create();
- LoanTransaction accrualTransaction = LoanTransaction.accrueTransaction(loan, loan.getOffice(), loan.getClosedOnDate(),
+ LocalDate accrualTransactionDate = getFinalAccrualTransactionDate(loan);
+
+ LoanTransaction accrualTransaction = LoanTransaction.accrueTransaction(loan, loan.getOffice(), accrualTransactionDate,
total.getAmount(), interestPortion.getAmount(), feePortion.getAmount(), penaltyPortion.getAmount(), externalId);
Set<LoanChargePaidBy> accrualCharges = accrualTransaction.getLoanChargesPaid();
@@ -967,4 +969,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
}
+ private LocalDate getFinalAccrualTransactionDate(Loan loan) {
+ return switch (loan.getStatus()) {
+ case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
+ case OVERPAID -> loan.getOverpaidOnDate();
+ default -> throw new IllegalStateException("Unexpected value: " + loan.getStatus());
+ };
+ }
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java
index 4f3dab667..14e379ea2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java
@@ -47,9 +47,9 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
final Loan loan = event.get();
log.debug("Loan Status change for loan {}", loan.getId());
- if (loan.getStatus().isClosedObligationsMet()) {
+ if (loan.getStatus().isClosedObligationsMet() || loan.getStatus().isOverpaid()) {
log.debug("Loan Status {} for loan {}", loan.getStatus().getCode(), loan.getId());
- loanAccountDomainService.applyIncomeAccrualTransaction(loan);
+ loanAccountDomainService.applyFinalIncomeAccrualTransaction(loan);
}
if (loan.isOpen()) {
loan.handleMaturityDateActivate();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 97bd28814..b5e6145eb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -1229,6 +1229,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LoanTransaction loanTransaction = this.loanTransactionRepository.findByIdAndLoanId(command.entityId(), command.getLoanId())
.orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId(), command.getLoanId()));
+ if (loanTransaction.isReversed()) {
+ throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+ "Loan transaction:" + transactionId + " chargeback not allowed as loan transaction repayment is reversed",
+ transactionId);
+ }
+
+ if (!loanTransaction.isRepaymentLikeType()) {
+ throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+ "Loan transaction:" + transactionId + " chargeback not allowed as loan transaction is not repayment type, its type is "
+ + loanTransaction.getTypeOf().getCode(),
+ transactionId);
+ }
+
Loan loan = this.loanAssembler.assembleFrom(loanId);
if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) {
throw new PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed",
@@ -1243,22 +1256,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
"Loan transaction:" + transactionId + " chargeback not allowed as loan product is interest recalculation enabled",
transactionId);
}
-
checkClientOrGroupActive(loan);
- if (loanTransaction.isReversed()) {
- throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
- "Loan transaction:" + transactionId + " chargeback not allowed as loan transaction repayment is reversed",
- transactionId);
- }
-
- if (!loanTransaction.isRepayment()) {
- throw new PlatformServiceUnavailableException(
- "error.msg.loan.chargeback.operation.not.allowed", "Loan transaction:" + transactionId
- + " chargeback not allowed as loan transaction is not repayment, is " + loanTransaction.getTypeOf().getCode(),
- transactionId);
- }
-
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
final List<Long> existingReversedTransactionIds = loan.findExistingReversedTransactionIds();
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DueDateRespectiveLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DueDateRespectiveLoanRepaymentScheduleTest.java
index d2629528b..4933ef598 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DueDateRespectiveLoanRepaymentScheduleTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DueDateRespectiveLoanRepaymentScheduleTest.java
@@ -610,17 +610,25 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
assertEquals(100.0, response.getTotalOverpaid());
assertTrue(response.getStatus().getOverpaid());
- assertEquals(secondRepaymentId, response.getTransactions().get(2).getId().intValue());
- assertNull(response.getTransactions().get(2).getReversedOnDate());
- assertTrue(response.getTransactions().get(2).getTransactionRelations().isEmpty());
- assertTrue(response.getTransactions().get(2).getType().getRepayment());
- assertEquals(650.0, response.getTransactions().get(2).getAmount());
- assertEquals(550.0, response.getTransactions().get(2).getPrincipalPortion());
- assertEquals(0.0, response.getTransactions().get(2).getPenaltyChargesPortion());
- assertEquals(100.0, response.getTransactions().get(2).getOverpaymentPortion());
- assertEquals(0.0, response.getTransactions().get(2).getInterestPortion());
- assertEquals(0.0, response.getTransactions().get(2).getFeeChargesPortion());
- assertEquals(0.0, response.getTransactions().get(2).getOutstandingLoanBalance());
+ int secondRepaymentIndex;
+ // The repayment and accrual order is not consistent
+ if (response.getTransactions().get(2).getType().getRepayment()) {
+ secondRepaymentIndex = 2;
+ } else {
+ secondRepaymentIndex = 3;
+ }
+
+ assertEquals(secondRepaymentId, response.getTransactions().get(secondRepaymentIndex).getId().intValue());
+ assertNull(response.getTransactions().get(secondRepaymentIndex).getReversedOnDate());
+ assertTrue(response.getTransactions().get(secondRepaymentIndex).getTransactionRelations().isEmpty());
+ assertTrue(response.getTransactions().get(secondRepaymentIndex).getType().getRepayment());
+ assertEquals(650.0, response.getTransactions().get(secondRepaymentIndex).getAmount());
+ assertEquals(550.0, response.getTransactions().get(secondRepaymentIndex).getPrincipalPortion());
+ assertEquals(0.0, response.getTransactions().get(secondRepaymentIndex).getPenaltyChargesPortion());
+ assertEquals(100.0, response.getTransactions().get(secondRepaymentIndex).getOverpaymentPortion());
+ assertEquals(0.0, response.getTransactions().get(secondRepaymentIndex).getInterestPortion());
+ assertEquals(0.0, response.getTransactions().get(secondRepaymentIndex).getFeeChargesPortion());
+ assertEquals(0.0, response.getTransactions().get(secondRepaymentIndex).getOutstandingLoanBalance());
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountOverpaidDateStatusTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountOverpaidDateStatusTest.java
index 63b9f593d..72f169d28 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountOverpaidDateStatusTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountOverpaidDateStatusTest.java
@@ -30,8 +30,6 @@ import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeFormatterBuilder;
import java.util.HashMap;
import java.util.UUID;
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
@@ -57,12 +55,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
public class LoanAccountOverpaidDateStatusTest {
private ResponseSpecification responseSpec;
- private ResponseSpecification responseSpecErr400;
- private ResponseSpecification responseSpecErr503;
private RequestSpecification requestSpec;
private ClientHelper clientHelper;
private LoanTransactionHelper loanTransactionHelper;
- private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
@BeforeEach
public void setup() {
@@ -70,8 +65,6 @@ public class LoanAccountOverpaidDateStatusTest {
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
- this.responseSpecErr400 = new ResponseSpecBuilder().expectStatusCode(400).build();
- this.responseSpecErr503 = new ResponseSpecBuilder().expectStatusCode(503).build();
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
}
@@ -124,7 +117,7 @@ public class LoanAccountOverpaidDateStatusTest {
GetLoansLoanIdResponse loanDetailsOverpaid = loanTransactionHelper.getLoanDetails((long) loanId);
assertTrue(loanDetailsOverpaid.getStatus().getOverpaid());
assertNotNull(loanDetailsOverpaid.getOverpaidOnDate());
- assertEquals(loanDetailsOverpaid.getOverpaidOnDate(), todaysDate);
+ assertEquals(loanDetailsOverpaid.getOverpaidOnDate(), LocalDate.of(2022, 9, 9));
// reverse repayment to make loan not overpaid and overpaid date is reset
loanTransactionHelper.reverseRepayment(loanId, repaymentTransaction_4.getResourceId().intValue(), "10 September 2022");
@@ -141,7 +134,7 @@ public class LoanAccountOverpaidDateStatusTest {
GetLoansLoanIdResponse loanDetailsOverpaid_1 = loanTransactionHelper.getLoanDetails((long) loanId);
assertTrue(loanDetailsOverpaid_1.getStatus().getOverpaid());
assertNotNull(loanDetailsOverpaid_1.getOverpaidOnDate());
- assertEquals(loanDetailsOverpaid_1.getOverpaidOnDate(), todaysDate);
+ assertEquals(loanDetailsOverpaid_1.getOverpaidOnDate(), LocalDate.of(2022, 9, 11));
// Credit balance refund to reset overpaid status
loanTransactionHelper.creditBalanceRefund("12 September 2022", Float.valueOf(100), null, loanId, "");
@@ -164,7 +157,7 @@ public class LoanAccountOverpaidDateStatusTest {
GetLoansLoanIdResponse loanDetailsOverpaid_3 = loanTransactionHelper.getLoanDetails((long) loanId);
assertTrue(loanDetailsOverpaid_3.getStatus().getOverpaid());
assertNotNull(loanDetailsOverpaid_3.getOverpaidOnDate());
- assertEquals(loanDetailsOverpaid_3.getOverpaidOnDate(), todaysDate);
+ assertEquals(loanDetailsOverpaid_3.getOverpaidOnDate(), LocalDate.of(2022, 9, 14));
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
index 330478d93..d52f64dc9 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
@@ -427,6 +427,84 @@ public class LoanChargeSpecificDueDateTest {
}
}
+ @Test
+ public void testApplyFeeAccrualWhenLoanOverpaid() {
+ try {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+
+ final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, todaysDate);
+
+ // Client and Loan account creation
+ final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithPeriodicAccrual(
+ loanTransactionHelper, null);
+ assertNotNull(getLoanProductsProductResponse);
+
+ LocalDate transactionDate = LocalDate.of(Utils.getLocalDateOfTenant().getYear(), 1, 1);
+ String operationDate = Utils.dateFormatter.format(transactionDate);
+ log.info("Disbursement date {}", transactionDate);
+
+ // Create Loan Account
+ final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
+ getLoanProductsProductResponse.getId().toString(), operationDate, "1", "0");
+
+ // Get loan details
+ GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("0.00"), true);
+
+ // Apply Loan Charge with specific due date
+ String feeAmount = "10.00";
+ String payloadJSON = ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, feeAmount, true);
+ final PostChargesResponse postChargesResponse = ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON);
+ assertNotNull(postChargesResponse);
+ final Long chargeId = postChargesResponse.getResourceId();
+ assertNotNull(chargeId);
+
+ // First Loan Charge
+ transactionDate = transactionDate.plusDays(1);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, transactionDate);
+ operationDate = Utils.dateFormatter.format(transactionDate);
+ log.info("Operation date {}", transactionDate);
+ payloadJSON = LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(chargeId.toString(), operationDate, feeAmount);
+ PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse = loanTransactionHelper.addChargeForLoan(loanId, payloadJSON,
+ responseSpec);
+ assertNotNull(postLoansLoanIdChargesResponse);
+ final Long loanChargeId01 = postLoansLoanIdChargesResponse.getResourceId();
+ assertNotNull(loanChargeId01);
+
+ Float amount = Float.valueOf("1020.00");
+ transactionDate = transactionDate.plusDays(2);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, transactionDate);
+ operationDate = Utils.dateFormatter.format(transactionDate);
+ log.info("Operation date {}", transactionDate);
+ PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
+ loanId);
+ assertNotNull(loanIdTransactionsResponse);
+ log.info("Loan Transaction Id: {} {}", loanId, loanIdTransactionsResponse.getResourceId());
+
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ assertNotNull(getLoansLoanIdResponse);
+ loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.overpaid");
+
+ final Long transactionId = loanTransactionHelper.evaluateLastLoanTransactionData(getLoansLoanIdResponse,
+ "loanTransactionType.accrual", operationDate, Double.valueOf("10.00"));
+ assertNotNull(transactionId);
+ log.info("transactionId {}", transactionId);
+
+ final GetJournalEntriesTransactionIdResponse journalEntriesResponse = journalEntryHelper.getJournalEntries("L" + transactionId);
+ assertNotNull(journalEntriesResponse);
+ final List<JournalEntryTransactionItem> journalEntries = journalEntriesResponse.getPageItems();
+ assertEquals(2, journalEntries.size());
+ assertEquals(10, journalEntries.get(0).getAmount());
+ assertEquals(10, journalEntries.get(1).getAmount());
+ assertEquals(transactionDate, journalEntries.get(0).getTransactionDate());
+ assertEquals(transactionDate, journalEntries.get(1).getTransactionDate());
+ } finally {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+ }
+ }
+
private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
final Integer delinquencyBucketId) {
final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);