FINERACT-1806: Charge-off API
This commit is contained in:
committed by
Arnold Gálovics
parent
bd052acab0
commit
c7504680b7
@@ -724,6 +724,30 @@
|
||||
"null",
|
||||
"org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "overpaidOnDate",
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "lastClosedBusinessDate",
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "chargedOff",
|
||||
"type": [
|
||||
"null",
|
||||
"boolean"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -242,6 +242,38 @@
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "chargedOffOnDate",
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "chargedOffByUsername",
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "chargedOffByFirstname",
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "chargedOffByLastname",
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -410,6 +410,22 @@
|
||||
"null",
|
||||
"bigdecimal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "chargeOffReasonId",
|
||||
"type": [
|
||||
"null",
|
||||
"long"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "chargeOffReason",
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+8
@@ -3606,4 +3606,12 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/externaleventconfiguration";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder chargeOff(final Long loanId) {
|
||||
this.actionName = "CHARGEOFF";
|
||||
this.entityName = "LOAN";
|
||||
this.loanId = loanId;
|
||||
this.href = "/loans/" + loanId + "/transactions?command=charge-off";
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -470,15 +470,17 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
||||
.append(" l.closedon_date as closedOnDate,")
|
||||
.append(" cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,")
|
||||
.append(" la.overdue_since_date_derived as overdueSinceDate, ")
|
||||
.append(" l.writtenoffon_date as writtenOffOnDate, l.expected_maturedon_date as expectedMaturityDate")
|
||||
|
||||
.append(" from m_loan l ").append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id")
|
||||
.append(" l.writtenoffon_date as writtenOffOnDate, l.expected_maturedon_date as expectedMaturityDate, ")
|
||||
.append(" l.charged_off_on_date as chargedOffOnDate, cobu.username as chargedOffByUsername, ")
|
||||
.append(" cobu.firstname as chargedOffByFirstname, cobu.lastname as chargedOffByLastname ").append(" from m_loan l ")
|
||||
.append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id")
|
||||
.append(" left join m_appuser sbu on sbu.id = l.created_by")
|
||||
.append(" left join m_appuser rbu on rbu.id = l.rejectedon_userid")
|
||||
.append(" left join m_appuser wbu on wbu.id = l.withdrawnon_userid")
|
||||
.append(" left join m_appuser abu on abu.id = l.approvedon_userid")
|
||||
.append(" left join m_appuser dbu on dbu.id = l.disbursedon_userid")
|
||||
.append(" left join m_appuser cbu on cbu.id = l.closedon_userid")
|
||||
.append(" left join m_appuser cobu on cobu.id = l.charged_off_by_userid")
|
||||
.append(" left join m_loan_arrears_aging la on la.loan_id = l.id")
|
||||
.append(" left join glim_accounts glim on glim.id=l.glim_id");
|
||||
|
||||
@@ -541,6 +543,12 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
||||
final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate");
|
||||
|
||||
final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
|
||||
|
||||
final LocalDate chargedOffOnDate = JdbcSupport.getLocalDate(rs, "chargedOffOnDate");
|
||||
final String chargedOffByUsername = rs.getString("chargedOffByUsername");
|
||||
final String chargedOffByFirstname = rs.getString("chargedOffByFirstname");
|
||||
final String chargedOffByLastname = rs.getString("chargedOffByLastname");
|
||||
|
||||
Boolean inArrears = (overdueSinceDate != null);
|
||||
|
||||
final LoanApplicationTimelineData timeline = new LoanApplicationTimelineData(submittedOnDate, submittedByUsername,
|
||||
@@ -548,7 +556,8 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
||||
withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername,
|
||||
approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername,
|
||||
disbursedByFirstname, disbursedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname,
|
||||
expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname);
|
||||
expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname, chargedOffOnDate,
|
||||
chargedOffByUsername, chargedOffByFirstname, chargedOffByLastname);
|
||||
|
||||
return new LoanAccountSummaryData(id, accountNo, parentAccountNumber, externalId, productId, loanProductName,
|
||||
shortLoanProductName, loanStatus, loanType, loanCycle, timeline, inArrears, originalLoan, loanBalance, amountPaid);
|
||||
|
||||
+3
-1
@@ -133,7 +133,8 @@ public interface LoanApiConstants {
|
||||
String installmentAmountParamName = "installmentAmount";
|
||||
// loan write off
|
||||
String WRITEOFFREASONS = "WriteOffReasons";
|
||||
|
||||
// loan charge-off
|
||||
String CHARGE_OFF_REASONS = "ChargeOffReasons";
|
||||
// fore closure constants
|
||||
String transactionDateParamName = "transactionDate";
|
||||
String noteParamName = "note";
|
||||
@@ -153,6 +154,7 @@ public interface LoanApiConstants {
|
||||
String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment";
|
||||
|
||||
String LOAN_ASSOCIATIONS_ALL = "all";
|
||||
String chargeOffReasonIdParamName = "chargeOffReasonId";
|
||||
|
||||
// Reversal Transation Data
|
||||
String REVERSAL_EXTERNAL_ID_PARAMNAME = "reversalExternalId";
|
||||
|
||||
+12
-3
@@ -76,6 +76,7 @@ import org.springframework.stereotype.Component;
|
||||
@Tag(name = "Loan Transactions", description = "Capabilities include loan repayment's, interest waivers and the ability to 'adjust' an existing transaction. An 'adjustment' of a transaction is really a 'reversal' of existing transaction followed by creation of a new transaction with the provided details.")
|
||||
public class LoanTransactionsApiResource {
|
||||
|
||||
public static final String CHARGE_OFF_COMMAND_VALUE = "charge-off";
|
||||
private final Set<String> responseDataParameters = new HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount", "externalId",
|
||||
LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, LoanApiConstants.REVERSED_ON_DATE_PARAMNAME));
|
||||
|
||||
@@ -103,7 +104,8 @@ public class LoanTransactionsApiResource {
|
||||
+ "loans/1/transactions/template?command=recoverypayment" + "\n" + "loans/1/transactions/template?command=prepayLoan" + "\n"
|
||||
+ "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
|
||||
+ "loans/1/transactions/template?command=foreclosure" + "\n"
|
||||
+ "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)")
|
||||
+ "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)" + "\n"
|
||||
+ "loans/1/transactions/template?command=charge-off" + "\n")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
|
||||
public String retrieveTransactionTemplate(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
|
||||
@@ -129,7 +131,8 @@ public class LoanTransactionsApiResource {
|
||||
+ "loans/1/transactions/template?command=recoverypayment" + "\n" + "loans/1/transactions/template?command=prepayLoan" + "\n"
|
||||
+ "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
|
||||
+ "loans/1/transactions/template?command=foreclosure" + "\n"
|
||||
+ "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)")
|
||||
+ "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)" + "\n"
|
||||
+ "loans/1/transactions/template?command=charge-off" + "\n")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
|
||||
public String retrieveTransactionTemplate(
|
||||
@@ -224,7 +227,8 @@ public class LoanTransactionsApiResource {
|
||||
+ " | Undo Loan Write-off | \n" + "loans/1/transactions?command=recoverypayment" + " | Make Recovery Payment | \n"
|
||||
+ "loans/1/transactions?command=refundByCash" + " | Make a Refund of an Active Loan by Cash | \n"
|
||||
+ "loans/1/transactions?command=foreclosure" + " | Foreclosure of an Active Loan | \n"
|
||||
+ "loans/1/transactions?command=creditBalanceRefund" + " | Credit Balance Refund" + " | \n")
|
||||
+ "loans/1/transactions?command=creditBalanceRefund" + " | Credit Balance Refund" + " | \n"
|
||||
+ "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=charge-off" + " | Charge-off Loan" + " | \n")
|
||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsRequest.class)))
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsResponse.class))) })
|
||||
@@ -257,6 +261,7 @@ public class LoanTransactionsApiResource {
|
||||
+ "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=foreclosure"
|
||||
+ " | Foreclosure of an Active Loan | \n"
|
||||
+ "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=creditBalanceRefund" + " | Credit Balance Refund"
|
||||
+ " | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=charge-off" + " | Charge-off Loan"
|
||||
+ " | \n")
|
||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsRequest.class)))
|
||||
@ApiResponses({
|
||||
@@ -456,6 +461,8 @@ public class LoanTransactionsApiResource {
|
||||
commandRequest = builder.loanForeclosure(resolvedLoanId).build();
|
||||
} else if (CommandParameterUtil.is(commandParam, "creditBalanceRefund")) {
|
||||
commandRequest = builder.creditBalanceRefund(resolvedLoanId).build();
|
||||
} else if (CommandParameterUtil.is(commandParam, CHARGE_OFF_COMMAND_VALUE)) {
|
||||
commandRequest = builder.chargeOff(resolvedLoanId).build();
|
||||
}
|
||||
|
||||
if (commandRequest == null) {
|
||||
@@ -529,6 +536,8 @@ public class LoanTransactionsApiResource {
|
||||
transactionData = this.loanReadPlatformService.retrieveLoanForeclosureTemplate(resolvedLoanId, transactionDate);
|
||||
} else if (CommandParameterUtil.is(commandParam, "creditBalanceRefund")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveCreditBalanceRefundTemplate(resolvedLoanId);
|
||||
} else if (CommandParameterUtil.is(commandParam, CHARGE_OFF_COMMAND_VALUE)) {
|
||||
transactionData = this.loanReadPlatformService.retrieveLoanChargeOffTemplate(resolvedLoanId);
|
||||
} else {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
|
||||
+4
-1
@@ -258,7 +258,10 @@ final class LoanTransactionsApiResourceSwagger {
|
||||
public Integer loanChargeId;
|
||||
@Schema(example = "28 June 2022")
|
||||
public String dueDate;
|
||||
|
||||
@Schema(example = "1")
|
||||
public Long chargeOffReasonId;
|
||||
@Schema(example = "1")
|
||||
public Long writeoffReasonId;
|
||||
}
|
||||
|
||||
@Schema(description = "PostLoansLoanIdTransactionsResponse")
|
||||
|
||||
+14
@@ -247,6 +247,14 @@ final class LoansApiResourceSwagger {
|
||||
public LocalDate expectedMaturityDate;
|
||||
@Schema(example = "[2012, 4, 3]")
|
||||
public LocalDate closedOnDate;
|
||||
@Schema(example = "[2012, 4, 10]")
|
||||
public LocalDate chargedOffOnDate;
|
||||
@Schema(example = "admin")
|
||||
public String chargedOffByUsername;
|
||||
@Schema(example = "App")
|
||||
public String chargedOffByFirstname;
|
||||
@Schema(example = "Administrator")
|
||||
public String chargedOffByLastname;
|
||||
}
|
||||
|
||||
static final class GetLoansLoanIdRepaymentSchedule {
|
||||
@@ -601,6 +609,10 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalRepaymentTransactionReversed;
|
||||
public Set<GetLoansLoanIdOverdueCharges> overdueCharges;
|
||||
@Schema(example = "1")
|
||||
public Long chargeOffReasonId;
|
||||
@Schema(example = "reason")
|
||||
public String chargeOffReason;
|
||||
}
|
||||
|
||||
static final class GetLoansLoanIdPaymentType {
|
||||
@@ -1028,6 +1040,8 @@ final class LoansApiResourceSwagger {
|
||||
public LocalDate lastClosedBusinessDate;
|
||||
@Schema(example = "[2013, 11, 1]")
|
||||
public LocalDate overpaidOnDate;
|
||||
@Schema(example = "false")
|
||||
public Boolean chargedOff;
|
||||
}
|
||||
|
||||
@Schema(description = "GetLoansResponse")
|
||||
|
||||
+14
-12
@@ -248,11 +248,12 @@ public class LoanAccountData {
|
||||
private String linkAccountId;
|
||||
private Long groupId;
|
||||
private LocalDate expectedDisbursementDate;
|
||||
private LocalDate overpaidOnDate;
|
||||
|
||||
private LocalDate overpaidOnDate;
|
||||
private CollectionData delinquent;
|
||||
private DelinquencyRangeData delinquencyRange;
|
||||
private LocalDate lastClosedBusinessDate;
|
||||
private Boolean chargedOff;
|
||||
|
||||
public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId,
|
||||
Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments,
|
||||
@@ -381,7 +382,7 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled)
|
||||
.setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent)
|
||||
.setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,7 +453,7 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled)
|
||||
.setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent)
|
||||
.setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
|
||||
@@ -620,7 +621,7 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(product.isEqualAmortization()).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled)
|
||||
.setFixedPrincipalPercentagePerInstallment(product.getFixedPrincipalPercentagePerInstallment()).setDelinquent(delinquent)
|
||||
.setDisallowExpectedDisbursements(product.getDisallowExpectedDisbursements()).setFraud(acc.fraud)
|
||||
.setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -654,7 +655,7 @@ public class LoanAccountData {
|
||||
final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo,
|
||||
final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment,
|
||||
final DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud,
|
||||
LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate) {
|
||||
LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff) {
|
||||
|
||||
final CollectionData delinquent = CollectionData.template();
|
||||
|
||||
@@ -694,7 +695,7 @@ public class LoanAccountData {
|
||||
.setClosureLoanAccountNo(closureLoanAccountNo).setTopupAmount(topupAmount).setIsEqualAmortization(isEqualAmortization)
|
||||
.setFixedPrincipalPercentagePerInstallment(fixedPrincipalPercentagePerInstallment).setDelinquent(delinquent)
|
||||
.setDelinquencyRange(delinquencyRange).setDisallowExpectedDisbursements(disallowExpectedDisbursements).setFraud(fraud)
|
||||
.setLastClosedBusinessDate(lastClosedBusinessDate).setOverpaidOnDate(overpaidOnDate);
|
||||
.setLastClosedBusinessDate(lastClosedBusinessDate).setOverpaidOnDate(overpaidOnDate).setChargedOff(chargedOff);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -778,7 +779,8 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(acc.isEqualAmortization)
|
||||
.setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment)
|
||||
.setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
|
||||
.setFraud(acc.fraud).setLastClosedBusinessDate(acc.getLastClosedBusinessDate()).setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setFraud(acc.fraud).setLastClosedBusinessDate(acc.getLastClosedBusinessDate()).setOverpaidOnDate(acc.overpaidOnDate)
|
||||
.setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
|
||||
@@ -853,7 +855,7 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled)
|
||||
.setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent)
|
||||
.setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
|
||||
@@ -950,7 +952,7 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled)
|
||||
.setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent)
|
||||
.setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData,
|
||||
@@ -1015,7 +1017,7 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled)
|
||||
.setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent)
|
||||
.setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
|
||||
@@ -1073,7 +1075,7 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled)
|
||||
.setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent)
|
||||
.setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
|
||||
@@ -1134,7 +1136,7 @@ public class LoanAccountData {
|
||||
.setIsEqualAmortization(acc.isEqualAmortization).setRates(acc.rates).setIsRatesEnabled(acc.isRatesEnabled)
|
||||
.setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment).setDelinquent(acc.delinquent)
|
||||
.setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate);
|
||||
.setFraud(acc.fraud).setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff);
|
||||
}
|
||||
|
||||
public static final Comparator<LoanAccountData> ClientNameComparator = (loan1, loan2) -> {
|
||||
|
||||
+11
-1
@@ -62,6 +62,11 @@ public class LoanApplicationTimelineData {
|
||||
private final String writeOffByFirstname;
|
||||
private final String writeOffByLastname;
|
||||
|
||||
private final LocalDate chargedOffOnDate;
|
||||
private final String chargedOffByUsername;
|
||||
private final String chargedOffByFirstname;
|
||||
private final String chargedOffByLastname;
|
||||
|
||||
public static LoanApplicationTimelineData templateDefault(final LocalDate expectedDisbursementDate) {
|
||||
|
||||
final LocalDate submittedOnDate = null;
|
||||
@@ -93,13 +98,18 @@ public class LoanApplicationTimelineData {
|
||||
final String writeOffByUsername = null;
|
||||
final String writeOffByFirstname = null;
|
||||
final String writeOffByLastname = null;
|
||||
final LocalDate chargedOffOnDate = null;
|
||||
final String chargedOffByUsername = null;
|
||||
final String chargedOffByFirstname = null;
|
||||
final String chargedOffByLastname = null;
|
||||
|
||||
return new LoanApplicationTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname,
|
||||
rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername,
|
||||
withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname,
|
||||
expectedDisbursementDate, actualDisbursementDate, disbursedByUsername, disbursedByFirstname, disbursedByLastname,
|
||||
closedOnDate, closedByUsername, closedByFirstname, closedByLastname, expectedMaturityDate, writeOffOnDate,
|
||||
writeOffByUsername, writeOffByFirstname, writeOffByLastname);
|
||||
writeOffByUsername, writeOffByFirstname, writeOffByLastname, chargedOffOnDate, chargedOffByUsername, chargedOffByFirstname,
|
||||
chargedOffByLastname);
|
||||
}
|
||||
|
||||
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData(final CurrencyData currency, final BigDecimal principal,
|
||||
|
||||
+12
-7
@@ -87,6 +87,8 @@ public class LoanSummaryData {
|
||||
private BigDecimal totalCreditBalanceRefundReversed;
|
||||
private BigDecimal totalRepaymentTransaction;
|
||||
private BigDecimal totalRepaymentTransactionReversed;
|
||||
private final Long chargeOffReasonId;
|
||||
private final String chargeOffReason;
|
||||
|
||||
public LoanSummaryData(final CurrencyData currency, final BigDecimal principalDisbursed, final BigDecimal principalAdjustments,
|
||||
final BigDecimal principalPaid, final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
|
||||
@@ -100,7 +102,7 @@ public class LoanSummaryData {
|
||||
final BigDecimal totalRepayment, final BigDecimal totalExpectedCostOfLoan, final BigDecimal totalCostOfLoan,
|
||||
final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
|
||||
final BigDecimal totalOverdue, final LocalDate overdueSinceDate, final Long writeoffReasonId, final String writeoffReason,
|
||||
final BigDecimal totalRecovered) {
|
||||
final BigDecimal totalRecovered, final Long chargeOffReasonId, final String chargeOffReason) {
|
||||
this.currency = currency;
|
||||
this.principalDisbursed = principalDisbursed;
|
||||
this.principalAdjustments = principalAdjustments;
|
||||
@@ -139,6 +141,8 @@ public class LoanSummaryData {
|
||||
this.writeoffReasonId = writeoffReasonId;
|
||||
this.writeoffReason = writeoffReason;
|
||||
this.totalRecovered = totalRecovered;
|
||||
this.chargeOffReasonId = chargeOffReasonId;
|
||||
this.chargeOffReason = chargeOffReason;
|
||||
}
|
||||
|
||||
public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryData defaultSummaryData,
|
||||
@@ -193,12 +197,13 @@ public class LoanSummaryData {
|
||||
defaultSummaryData.totalExpectedRepayment, defaultSummaryData.totalRepayment, defaultSummaryData.totalExpectedCostOfLoan,
|
||||
defaultSummaryData.totalCostOfLoan, defaultSummaryData.totalWaived, defaultSummaryData.totalWrittenOff,
|
||||
defaultSummaryData.totalOutstanding, defaultSummaryData.totalOverdue, defaultSummaryData.overdueSinceDate,
|
||||
defaultSummaryData.writeoffReasonId, defaultSummaryData.writeoffReason, defaultSummaryData.totalRecovered)
|
||||
.setTotalMerchantRefund(totalMerchantRefund).setTotalMerchantRefundReversed(totalMerchantRefundReversed)
|
||||
.setTotalPayoutRefund(totalPayoutRefund).setTotalPayoutRefundReversed(totalPayoutRefundReversed)
|
||||
.setTotalGoodwillCredit(totalGoodwillCredit).setTotalGoodwillCreditReversed(totalGoodwillCreditReversed)
|
||||
.setTotalChargeAdjustment(totalChargeAdjustment).setTotalChargeAdjustmentReversed(totalChargeAdjustmentReversed)
|
||||
.setTotalChargeback(totalChargeback).setTotalCreditBalanceRefund(totalCreditBalanceRefund)
|
||||
defaultSummaryData.writeoffReasonId, defaultSummaryData.writeoffReason, defaultSummaryData.totalRecovered,
|
||||
defaultSummaryData.chargeOffReasonId, defaultSummaryData.chargeOffReason).setTotalMerchantRefund(totalMerchantRefund)
|
||||
.setTotalMerchantRefundReversed(totalMerchantRefundReversed).setTotalPayoutRefund(totalPayoutRefund)
|
||||
.setTotalPayoutRefundReversed(totalPayoutRefundReversed).setTotalGoodwillCredit(totalGoodwillCredit)
|
||||
.setTotalGoodwillCreditReversed(totalGoodwillCreditReversed).setTotalChargeAdjustment(totalChargeAdjustment)
|
||||
.setTotalChargeAdjustmentReversed(totalChargeAdjustmentReversed).setTotalChargeback(totalChargeback)
|
||||
.setTotalCreditBalanceRefund(totalCreditBalanceRefund)
|
||||
.setTotalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed)
|
||||
.setTotalRepaymentTransaction(totalRepaymentTransaction)
|
||||
.setTotalRepaymentTransactionReversed(totalRepaymentTransactionReversed);
|
||||
|
||||
+6
@@ -96,6 +96,8 @@ public class LoanTransactionData {
|
||||
|
||||
private List<LoanTransactionRelationData> transactionRelations;
|
||||
|
||||
private Collection<CodeValueData> chargeOffReasonOptions = null;
|
||||
|
||||
public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
|
||||
Integer rowIndex, String locale, String dateFormat) {
|
||||
return new LoanTransactionData(repaymentAmount, lastRepaymentDate, repaymentTypeId, rowIndex, locale, dateFormat);
|
||||
@@ -369,6 +371,10 @@ public class LoanTransactionData {
|
||||
this.writeOffReasonOptions = writeOffReasonOptions;
|
||||
}
|
||||
|
||||
public void setChargeOffReasonOptions(Collection<CodeValueData> chargeOffReasonOptions) {
|
||||
this.chargeOffReasonOptions = chargeOffReasonOptions;
|
||||
}
|
||||
|
||||
public void setLoanChargePaidByList(Collection<LoanChargePaidByData> loanChargePaidByList) {
|
||||
this.loanChargePaidByList = loanChargePaidByList;
|
||||
}
|
||||
|
||||
+33
@@ -439,6 +439,20 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
@Column(name = "last_closed_business_date")
|
||||
private LocalDate lastClosedBusinessDate;
|
||||
|
||||
@Column(name = "is_charged_off", nullable = false)
|
||||
private boolean chargedOff;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "charge_off_reason_cv_id")
|
||||
private CodeValue chargeOffReason;
|
||||
|
||||
@Column(name = "charged_off_on_date")
|
||||
private LocalDate chargedOffOnDate;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "charged_off_by_userid")
|
||||
private AppUser chargedOffBy;
|
||||
|
||||
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
|
||||
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
||||
final String transactionProcessingStrategyCode, final LoanProductRelatedDetail loanRepaymentScheduleDetail,
|
||||
@@ -6949,4 +6963,23 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
public void setLastClosedBusinessDate(LocalDate lastClosedBusinessDate) {
|
||||
this.lastClosedBusinessDate = lastClosedBusinessDate;
|
||||
}
|
||||
|
||||
public void markAsChargedOff(final LocalDate chargedOffOn, final AppUser chargedOffBy, final CodeValue chargeOffReason) {
|
||||
this.chargedOff = true;
|
||||
this.chargedOffBy = chargedOffBy;
|
||||
this.chargedOffOnDate = chargedOffOn;
|
||||
this.chargeOffReason = chargeOffReason;
|
||||
}
|
||||
|
||||
public void liftChargeOff() {
|
||||
this.chargedOff = false;
|
||||
this.chargedOffBy = null;
|
||||
this.chargedOffOnDate = null;
|
||||
this.chargeOffReason = null;
|
||||
}
|
||||
|
||||
public boolean isChargedOff() {
|
||||
return this.chargedOff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+25
-1
@@ -347,6 +347,25 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId);
|
||||
}
|
||||
|
||||
public static LoanTransaction chargeOff(final Loan loan, final LocalDate chargeOffDate, final ExternalId externalId) {
|
||||
BigDecimal principalPortion = loan.getLoanSummary().getTotalPrincipalOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getLoanSummary().getTotalPrincipalOutstanding()
|
||||
: null;
|
||||
BigDecimal interestPortion = loan.getLoanSummary().getTotalInterestOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getLoanSummary().getTotalInterestOutstanding()
|
||||
: null;
|
||||
BigDecimal feePortion = loan.getLoanSummary().getTotalFeeChargesOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getLoanSummary().getTotalFeeChargesOutstanding()
|
||||
: null;
|
||||
BigDecimal penaltyPortion = loan.getLoanSummary().getTotalPenaltyChargesOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getLoanSummary().getTotalPenaltyChargesOutstanding()
|
||||
: null;
|
||||
BigDecimal totalOutstanding = loan.getLoanSummary().getTotalOutstanding();
|
||||
|
||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_OFF.getValue(), chargeOffDate, totalOutstanding,
|
||||
principalPortion, interestPortion, feePortion, penaltyPortion, null, false, null, externalId);
|
||||
}
|
||||
|
||||
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final BigDecimal amount,
|
||||
final LocalDate date, final ExternalId externalId) {
|
||||
this.loan = loan;
|
||||
@@ -647,6 +666,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
return getTypeOf().isWriteOff() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isChargeOff() {
|
||||
return getTypeOf().isChargeOff() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isIdentifiedBy(final Long identifier) {
|
||||
return getId().equals(identifier);
|
||||
}
|
||||
@@ -786,7 +809,8 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
return isNotReversed() && (LoanTransactionType.CONTRA.equals(getTypeOf())
|
||||
|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
|
||||
|| LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf()) || LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
|
||||
|| LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf()) || LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf()));
|
||||
|| LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf()) || LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf())
|
||||
|| LoanTransactionType.CHARGE_OFF.equals(getTypeOf()));
|
||||
}
|
||||
|
||||
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
||||
|
||||
+6
-1
@@ -58,7 +58,8 @@ public enum LoanTransactionType {
|
||||
GOODWILL_CREDIT(23, "loanTransactionType.goodwillCredit"), //
|
||||
CHARGE_REFUND(24, "loanTransactionType.chargeRefund"), //
|
||||
CHARGEBACK(25, "loanTransactionType.chargeback"), //
|
||||
CHARGE_ADJUSTMENT(26, "loanTransactionType.chargeAdjustment");
|
||||
CHARGE_ADJUSTMENT(26, "loanTransactionType.chargeAdjustment"), //
|
||||
CHARGE_OFF(27, "loanTransactionType.chargeOff");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
@@ -100,6 +101,7 @@ public enum LoanTransactionType {
|
||||
case 24 -> LoanTransactionType.CHARGE_REFUND;
|
||||
case 25 -> LoanTransactionType.CHARGEBACK;
|
||||
case 26 -> LoanTransactionType.CHARGE_ADJUSTMENT;
|
||||
case 27 -> LoanTransactionType.CHARGE_OFF;
|
||||
default -> LoanTransactionType.INVALID;
|
||||
};
|
||||
}
|
||||
@@ -184,4 +186,7 @@ public enum LoanTransactionType {
|
||||
return this.equals(LoanTransactionType.CHARGE_ADJUSTMENT);
|
||||
}
|
||||
|
||||
public boolean isChargeOff() {
|
||||
return this.equals(LoanTransactionType.CHARGE_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.handler;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@CommandType(entity = "LOAN", action = "CHARGEOFF")
|
||||
public class ChargeOffLoanCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanWritePlatformService writePlatformService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return writePlatformService.chargeOff(command);
|
||||
}
|
||||
}
|
||||
+30
@@ -341,6 +341,36 @@ public final class LoanEventApiJsonValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateChargeOffTransaction(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Set<String> chargeOffParameters = new HashSet<>(
|
||||
Arrays.asList("transactionDate", "note", "locale", "dateFormat", "chargeOffReasonId", "externalId"));
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, chargeOffParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
|
||||
|
||||
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
|
||||
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
|
||||
|
||||
final String note = fromApiJsonHelper.extractStringNamed("note", element);
|
||||
baseDataValidator.reset().parameter("note").value(note).ignoreIfNull().notExceedingLengthOf(1000);
|
||||
|
||||
final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element);
|
||||
baseDataValidator.reset().parameter("externalId").value(externalId).ignoreIfNull().notExceedingLengthOf(100);
|
||||
|
||||
final Long chargeOffReasonId = fromApiJsonHelper.extractLongNamed("chargeOffReasonId", element);
|
||||
baseDataValidator.reset().parameter("chargeOffReasonId").value(chargeOffReasonId).ignoreIfNull().integerGreaterThanZero();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateUpdateOfLoanOfficer(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
|
||||
+2
@@ -116,6 +116,8 @@ public interface LoanReadPlatformService {
|
||||
|
||||
Collection<LoanScheduleAccrualData> retrievePeriodicAccrualData(LocalDate tillDate);
|
||||
|
||||
LoanTransactionData retrieveLoanChargeOffTemplate(Long loanId);
|
||||
|
||||
Collection<Long> fetchLoansForInterestRecalculation();
|
||||
|
||||
List<Long> fetchLoansForInterestRecalculation(Integer pageSize, Long maxLoanIdInList, String officeHierarchy);
|
||||
|
||||
+38
-4
@@ -677,7 +677,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
+ " lpvi.minimum_gap as minimuminstallmentgap, lpvi.maximum_gap as maximuminstallmentgap, "
|
||||
+ " lp.can_use_for_topup as canUseForTopup, l.is_topup as isTopup, topup.closure_loan_id as closureLoanId, "
|
||||
+ " l.total_recovered_derived as totalRecovered, topuploan.account_no as closureLoanAccountNo, "
|
||||
+ " topup.topup_amount as topupAmount, l.last_closed_business_date as lastClosedBusinessDate,l.overpaidon_date as overpaidOnDate from m_loan l" //
|
||||
+ " topup.topup_amount as topupAmount, l.last_closed_business_date as lastClosedBusinessDate,l.overpaidon_date as overpaidOnDate, "
|
||||
+ " l.is_charged_off as isChargedOff, l.charge_off_reason_cv_id as chargeOffReasonId, codec.code_value as chargeOffReason, l.charged_off_on_date as chargedOffOnDate, "
|
||||
+ " cobu.username as chargedOffByUsername, cobu.firstname as chargedOffByFirstname, cobu.lastname as chargedOffByLastname "
|
||||
+ " from m_loan l" //
|
||||
+ " join m_product_loan lp on lp.id = l.product_id" //
|
||||
+ " left join m_loan_recalculation_details lir on lir.loan_id = l.id join m_currency rc on rc."
|
||||
+ sqlGenerator.escape("code") + " = l.currency_code" //
|
||||
@@ -690,8 +693,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
+ " left join m_appuser wbu on wbu.id = l.withdrawnon_userid"
|
||||
+ " left join m_appuser abu on abu.id = l.approvedon_userid"
|
||||
+ " left join m_appuser dbu on dbu.id = l.disbursedon_userid left join m_appuser cbu on cbu.id = l.closedon_userid"
|
||||
+ " left join m_appuser cobu on cobu.id = l.charged_off_by_userid "
|
||||
+ " left join m_code_value cv on cv.id = l.loanpurpose_cv_id"
|
||||
+ " left join m_code_value codev on codev.id = l.writeoff_reason_cv_id"
|
||||
+ " left join m_code_value codec on codec.id = l.charge_off_reason_cv_id"
|
||||
+ " left join m_product_loan_variable_installment_config lpvi on lpvi.loan_product_id = l.product_id"
|
||||
+ " left join m_loan_topup as topup on l.id = topup.loan_id"
|
||||
+ " left join m_loan as topuploan on topuploan.id = topup.closure_loan_id ";
|
||||
@@ -792,12 +797,21 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final Integer minimumGap = rs.getInt("minimuminstallmentgap");
|
||||
final Integer maximumGap = rs.getInt("maximuminstallmentgap");
|
||||
|
||||
final LocalDate chargedOffOnDate = JdbcSupport.getLocalDate(rs, "chargedOffOnDate");
|
||||
final String chargedOffByUsername = rs.getString("chargedOffByUsername");
|
||||
final String chargedOffByFirstname = rs.getString("chargedOffByFirstname");
|
||||
final String chargedOffByLastname = rs.getString("chargedOffByLastname");
|
||||
final Long chargeOffReasonId = JdbcSupport.getLong(rs, "chargeOffReasonId");
|
||||
final String chargeOffReason = rs.getString("chargeOffReason");
|
||||
final boolean isChargedOff = rs.getBoolean("isChargedOff");
|
||||
|
||||
final LoanApplicationTimelineData timeline = new LoanApplicationTimelineData(submittedOnDate, submittedByUsername,
|
||||
submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname,
|
||||
withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername,
|
||||
approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername,
|
||||
disbursedByFirstname, disbursedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname,
|
||||
expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname);
|
||||
expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname, chargedOffOnDate,
|
||||
chargedOffByUsername, chargedOffByFirstname, chargedOffByLastname);
|
||||
|
||||
final BigDecimal principal = rs.getBigDecimal("principal");
|
||||
final BigDecimal approvedPrincipal = rs.getBigDecimal("approvedPrincipal");
|
||||
@@ -914,7 +928,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
penaltyChargesCharged, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff,
|
||||
penaltyChargesOutstanding, penaltyChargesOverdue, totalExpectedRepayment, totalRepayment, totalExpectedCostOfLoan,
|
||||
totalCostOfLoan, totalWaived, totalWrittenOff, totalOutstanding, totalOverdue, overdueSinceDate, writeoffReasonId,
|
||||
writeoffReason, totalRecovered);
|
||||
writeoffReason, totalRecovered, chargeOffReasonId, chargeOffReason);
|
||||
}
|
||||
|
||||
GroupGeneralData groupData = null;
|
||||
@@ -1022,7 +1036,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, maximumGap, loanSubStatus,
|
||||
canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization,
|
||||
fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud,
|
||||
lastClosedBusinessDate, overpaidOnDate);
|
||||
lastClosedBusinessDate, overpaidOnDate, isChargedOff);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1917,6 +1931,26 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
return loanTransactionData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanTransactionData retrieveLoanChargeOffTemplate(final Long loanId) {
|
||||
|
||||
final LoanAccountData loan = this.retrieveOne(loanId);
|
||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.CHARGE_OFF);
|
||||
final BigDecimal totalOutstanding = loan.getSummary() != null ? loan.getSummary().getTotalOutstanding() : null;
|
||||
final BigDecimal totalPrincipalOutstanding = loan.getSummary() != null ? loan.getSummary().getPrincipalOutstanding() : null;
|
||||
final BigDecimal totalInterestOutstanding = loan.getSummary() != null ? loan.getSummary().getInterestOutstanding() : null;
|
||||
final BigDecimal totalFeeOutstanding = loan.getSummary() != null ? loan.getSummary().getFeeChargesOutstanding() : null;
|
||||
final BigDecimal totalPenaltyOutstanding = loan.getSummary() != null ? loan.getSummary().getPenaltyChargesOutstanding() : null;
|
||||
final List<CodeValueData> chargeOffReasonOptions = new ArrayList<>(
|
||||
this.codeValueReadPlatformService.retrieveCodeValuesByCode(LoanApiConstants.CHARGE_OFF_REASONS));
|
||||
LoanTransactionData loanTransactionData = new LoanTransactionData(null, null, null, transactionType, null, loan.getCurrency(),
|
||||
DateUtils.getBusinessLocalDate(), totalOutstanding, loan.getNetDisbursalAmount(), totalPrincipalOutstanding,
|
||||
totalInterestOutstanding, totalFeeOutstanding, totalPenaltyOutstanding, null, ExternalId.empty(), null, null, null, null,
|
||||
false, loanId, loan.getExternalId());
|
||||
loanTransactionData.setChargeOffReasonOptions(chargeOffReasonOptions);
|
||||
return loanTransactionData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Long> fetchLoansForInterestRecalculation() {
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
|
||||
+1
@@ -111,4 +111,5 @@ public interface LoanWritePlatformService {
|
||||
|
||||
CommandProcessingResult markLoanAsFraud(Long loanId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult chargeOff(JsonCommand command);
|
||||
}
|
||||
|
||||
+52
@@ -2644,6 +2644,58 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult chargeOff(JsonCommand command) {
|
||||
|
||||
loanEventApiJsonValidator.validateChargeOffTransaction(command.json());
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
changes.put(LoanApiConstants.transactionDateParamName,
|
||||
command.stringValueOfParameterNamed(LoanApiConstants.transactionDateParamName));
|
||||
changes.put(LoanApiConstants.localeParameterName, command.locale());
|
||||
changes.put(LoanApiConstants.dateFormatParameterName, command.dateFormat());
|
||||
|
||||
// TODO: add business logic validation (transaction date cannot be future, cannot be earlier than latest
|
||||
// transactions, etc)
|
||||
Loan loan = loanAssembler.assembleFrom(command.getLoanId());
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed(LoanApiConstants.transactionDateParamName);
|
||||
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
|
||||
final AppUser currentUser = getAppUserIfPresent();
|
||||
|
||||
if (command.hasParameter(LoanApiConstants.chargeOffReasonIdParamName)) {
|
||||
Long chargeOffReasonId = command.longValueOfParameterNamed(LoanApiConstants.chargeOffReasonIdParamName);
|
||||
CodeValue chargeOffReason = this.codeValueRepository
|
||||
.findOneByCodeNameAndIdWithNotFoundDetection(LoanApiConstants.CHARGE_OFF_REASONS, chargeOffReasonId);
|
||||
changes.put(LoanApiConstants.chargeOffReasonIdParamName, chargeOffReasonId);
|
||||
loan.markAsChargedOff(transactionDate, currentUser, chargeOffReason);
|
||||
} else {
|
||||
loan.markAsChargedOff(transactionDate, currentUser, null);
|
||||
}
|
||||
|
||||
LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId);
|
||||
loanTransactionRepository.saveAndFlush(chargeOffTransaction);
|
||||
|
||||
String noteText = command.stringValueOfParameterNamed(LoanApiConstants.noteParameterName);
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
changes.put(LoanApiConstants.noteParameterName, noteText);
|
||||
final Note note = Note.loanTransactionNote(loan, chargeOffTransaction, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
// TODO: add accounting
|
||||
// TODO: add external events
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(chargeOffTransaction.getId()) //
|
||||
.withEntityExternalId(chargeOffTransaction.getExternalId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withGroupId(loan.getGroupId()) //
|
||||
.withLoanId(command.getLoanId()) //
|
||||
.with(changes).build();
|
||||
}
|
||||
|
||||
private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) {
|
||||
if (!loan.isMultiDisburmentLoan()) {
|
||||
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
|
||||
|
||||
+2
@@ -291,6 +291,8 @@ public final class LoanEnumerations {
|
||||
LoanTransactionType.CHARGEBACK.getCode(), "Chargeback");
|
||||
case CHARGE_ADJUSTMENT -> new LoanTransactionEnumData(LoanTransactionType.CHARGE_ADJUSTMENT.getValue().longValue(),
|
||||
LoanTransactionType.CHARGE_ADJUSTMENT.getCode(), "Charge Adjustment");
|
||||
case CHARGE_OFF -> new LoanTransactionEnumData(LoanTransactionType.CHARGE_OFF.getValue().longValue(),
|
||||
LoanTransactionType.CHARGE_OFF.getCode(), "Charge-off");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -165,9 +165,9 @@ final class SelfLoansApiResourceSwagger {
|
||||
public String description;
|
||||
}
|
||||
|
||||
static final class GetLoansLoanIdTimeline {
|
||||
static final class GetSelfLoanIdTimeline {
|
||||
|
||||
private GetLoansLoanIdTimeline() {}
|
||||
private GetSelfLoanIdTimeline() {}
|
||||
|
||||
@Schema(example = "[2012, 4, 3]")
|
||||
public LocalDate submittedOnDate;
|
||||
@@ -201,9 +201,9 @@ final class SelfLoansApiResourceSwagger {
|
||||
public LocalDate closedOnDate;
|
||||
}
|
||||
|
||||
static final class GetLoansLoanIdSummary {
|
||||
static final class GetSelfLoanIdSummary {
|
||||
|
||||
private GetLoansLoanIdSummary() {}
|
||||
private GetSelfLoanIdSummary() {}
|
||||
|
||||
static final class GetLoansLoanIdEmiVariations {
|
||||
|
||||
@@ -510,8 +510,8 @@ final class SelfLoansApiResourceSwagger {
|
||||
public GetLoansLoanIdInterestCalculationPeriodType interestCalculationPeriodType;
|
||||
@Schema(example = "mifos-standard-strategy")
|
||||
public String transactionProcessingStrategyCode;
|
||||
public GetLoansLoanIdTimeline timeline;
|
||||
public GetLoansLoanIdSummary summary;
|
||||
public GetSelfLoanIdTimeline timeline;
|
||||
public GetSelfLoanIdSummary summary;
|
||||
}
|
||||
|
||||
@Schema(description = "GetSelfLoansLoanIdTransactionsTransactionIdResponse")
|
||||
@@ -554,7 +554,7 @@ final class SelfLoansApiResourceSwagger {
|
||||
public LocalDate date;
|
||||
@Schema(example = "false")
|
||||
public Boolean manuallyReversed;
|
||||
public GetSelfLoansLoanIdResponse.GetLoansLoanIdSummary.GetLoansLoanIdOverdueCharges.GetLoanCurrency currency;
|
||||
public GetSelfLoansLoanIdResponse.GetSelfLoanIdSummary.GetLoansLoanIdOverdueCharges.GetLoanCurrency currency;
|
||||
@Schema(example = "559.88")
|
||||
public Float amount;
|
||||
@Schema(example = "559.88")
|
||||
@@ -602,7 +602,7 @@ final class SelfLoansApiResourceSwagger {
|
||||
public Double percentage;
|
||||
@Schema(example = "0")
|
||||
public Double amountPercentageAppliedTo;
|
||||
public GetSelfLoansLoanIdResponse.GetLoansLoanIdSummary.GetLoansLoanIdOverdueCharges.GetLoanCurrency currency;
|
||||
public GetSelfLoansLoanIdResponse.GetSelfLoanIdSummary.GetLoansLoanIdOverdueCharges.GetLoanCurrency currency;
|
||||
@Schema(example = "100")
|
||||
public Float amount;
|
||||
@Schema(example = "0")
|
||||
|
||||
@@ -98,4 +98,5 @@
|
||||
<include file="parts/0076_add_loan_transaction_enum_values.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0077_add_overpaid_date_for_loan.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0078_add_configuration_cob_bulk_event.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0079_add_charge_off_details_to_loan.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?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.3.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<insert tableName="m_code">
|
||||
<column name="code_name" value="ChargeOffReasons"/>
|
||||
<column name="is_system_defined" valueBoolean="true"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
<changeSet author="fineract" id="2">
|
||||
<addColumn tableName="m_loan">
|
||||
<column defaultValueBoolean="false" name="is_charged_off" type="boolean">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValueComputed="NULL" name="charged_off_on_date" type="date"/>
|
||||
<column defaultValueComputed="NULL" name="charge_off_reason_cv_id" type="BIGINT"/>
|
||||
<column defaultValueComputed="NULL" name="charged_off_by_userid" type="BIGINT"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
<changeSet id="3" author="fineract">
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="transaction_loan"/>
|
||||
<column name="code" value="CHARGEOFF_LOAN"/>
|
||||
<column name="entity_name" value="LOAN"/>
|
||||
<column name="action_name" value="CHARGEOFF"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+55
@@ -19,7 +19,9 @@
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -89,6 +91,7 @@ import org.apache.fineract.integrationtests.common.savings.AccountTransferHelper
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
|
||||
import org.apache.fineract.integrationtests.common.system.CodeHelper;
|
||||
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
|
||||
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
||||
@@ -6939,6 +6942,58 @@ public class ClientLoanIntegrationTest {
|
||||
assertEquals(0.0f, loanSummary.get("totalWaived"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chargeOff() {
|
||||
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||
String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5);
|
||||
Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
|
||||
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount, expenseAccount,
|
||||
overpaymentAccount);
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011");
|
||||
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID);
|
||||
|
||||
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanID, "1000");
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||
assertTrue(loanDetails.getStatus().getActive());
|
||||
assertEquals(1000.0, loanDetails.getSummary().getTotalOutstanding());
|
||||
assertFalse(loanDetails.getChargedOff());
|
||||
assertNull(loanDetails.getSummary().getChargeOffReasonId());
|
||||
assertNull(loanDetails.getSummary().getChargeOffReason());
|
||||
assertNull(loanDetails.getTimeline().getChargedOffOnDate());
|
||||
assertNull(loanDetails.getTimeline().getChargedOffByUsername());
|
||||
assertNull(loanDetails.getTimeline().getChargedOffByFirstname());
|
||||
assertNull(loanDetails.getTimeline().getChargedOffByLastname());
|
||||
|
||||
String transactionExternalId = UUID.randomUUID().toString();
|
||||
this.loanTransactionHelper.chargeOffLoan((long) loanID, new PostLoansLoanIdTransactionsRequest().transactionDate("4 September 2022")
|
||||
.locale("en").dateFormat("dd MMMM yyyy").externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
|
||||
|
||||
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||
assertTrue(loanDetails.getStatus().getActive());
|
||||
assertEquals(1000.0, loanDetails.getSummary().getTotalOutstanding());
|
||||
assertTrue(loanDetails.getChargedOff());
|
||||
assertEquals((long) chargeOffReasonId, loanDetails.getSummary().getChargeOffReasonId());
|
||||
assertEquals(randomText, loanDetails.getSummary().getChargeOffReason());
|
||||
assertEquals(LocalDate.of(2022, 9, 4), loanDetails.getTimeline().getChargedOffOnDate());
|
||||
assertEquals("mifos", loanDetails.getTimeline().getChargedOffByUsername());
|
||||
assertEquals("App", loanDetails.getTimeline().getChargedOffByFirstname());
|
||||
assertEquals("Administrator", loanDetails.getTimeline().getChargedOffByLastname());
|
||||
}
|
||||
|
||||
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID) {
|
||||
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
|
||||
|
||||
+18
@@ -188,6 +188,7 @@ public class ExternalIdSupportIntegrationTest extends IntegrationTest {
|
||||
loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "refundbycash", null, null, null);
|
||||
loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "foreclosure", null, null, null);
|
||||
loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "creditBalanceRefund", null, null, null);
|
||||
loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "charge-off", null, null, null);
|
||||
|
||||
// Check whether an external id was generated
|
||||
String waiveChargeExternalIdStr = UUID.randomUUID().toString();
|
||||
@@ -1069,6 +1070,23 @@ public class ExternalIdSupportIntegrationTest extends IntegrationTest {
|
||||
.externalId(transactionExternalId2));
|
||||
assertEquals(transactionExternalId2, forecloseResult.getResourceExternalId());
|
||||
|
||||
String loanExternalIdStr16 = UUID.randomUUID().toString();
|
||||
String transactionExternalId3 = UUID.randomUUID().toString();
|
||||
applyForLoanApplication(client.getClientId().intValue(), loanProductID, loanExternalIdStr16);
|
||||
this.loanTransactionHelper.approveLoan(loanExternalIdStr16,
|
||||
new PostLoansLoanIdRequest().approvedOnDate("2 September 2022").approvedLoanAmount(new BigDecimal("1000"))
|
||||
.expectedDisbursementDate("2 September 2022").locale("en").dateFormat("dd MMMM yyyy"));
|
||||
this.loanTransactionHelper.disburseLoan(loanExternalIdStr16,
|
||||
new PostLoansLoanIdRequest().actualDisbursementDate("2 September 2022").transactionAmount(new BigDecimal("1000"))
|
||||
.locale("en").dateFormat("dd MMMM yyyy"));
|
||||
this.loanTransactionHelper.disburseLoan(loanExternalIdStr16,
|
||||
new PostLoansLoanIdRequest().actualDisbursementDate("2 September 2022").transactionAmount(new BigDecimal("1000"))
|
||||
.locale("en").dateFormat("dd MMMM yyyy"));
|
||||
PostLoansLoanIdTransactionsResponse chargeOffResult = this.loanTransactionHelper.chargeOffLoan(loanExternalIdStr16,
|
||||
new PostLoansLoanIdTransactionsRequest().transactionDate("2 September 2022").locale("en").dateFormat("dd MMMM yyyy")
|
||||
.externalId(transactionExternalId3));
|
||||
assertEquals(transactionExternalId3, chargeOffResult.getResourceExternalId());
|
||||
|
||||
} finally {
|
||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
|
||||
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 50, false);
|
||||
|
||||
+8
@@ -1719,4 +1719,12 @@ public class LoanTransactionHelper extends IntegrationTest {
|
||||
public PostLoansLoanIdTransactionsResponse forecloseLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
||||
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "foreclosure"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse chargeOffLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
||||
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "charge-off"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse chargeOffLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) {
|
||||
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "charge-off"));
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -34,6 +34,7 @@ public final class CodeHelper {
|
||||
private static final String COUNTRY_CODE_NAME = "COUNTRY";
|
||||
private static final String STATE_CODE_NAME = "STATE";
|
||||
private static final String ADDRESS_TYPE_CODE_NAME = "ADDRESS_TYPE";
|
||||
private static final String CHARGE_OFF_REASONS_CODE_NAME = "ChargeOffReasons";
|
||||
|
||||
private CodeHelper() {
|
||||
|
||||
@@ -175,6 +176,13 @@ public final class CodeHelper {
|
||||
return createCodeValue(requestSpec, responseSpec, countryCode, countryName, position);
|
||||
}
|
||||
|
||||
public static Integer createChargeOffCodeValue(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||
final String value, final Integer position) {
|
||||
HashMap<String, Object> code = getCodeByName(requestSpec, responseSpec, CHARGE_OFF_REASONS_CODE_NAME);
|
||||
Integer countryCode = (Integer) code.get("id");
|
||||
return createCodeValue(requestSpec, responseSpec, countryCode, value, position);
|
||||
}
|
||||
|
||||
public static Integer createCodeValue(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||
final Integer codeId, final String codeValueName, final Integer position) {
|
||||
return (Integer) createCodeValue(requestSpec, responseSpec, codeId, codeValueName, position, SUBRESPONSE_ID_ATTRIBUTE_NAME);
|
||||
|
||||
Reference in New Issue
Block a user