FINERACT-2317: Add new endpoints to indirectly modify loan approved amount
This commit is contained in:
+9
@@ -3894,4 +3894,13 @@ public class CommandWrapperBuilder {
|
|||||||
this.href = "/loans/" + loanId + "/transactions/" + transactionId + "?command=interest-refund";
|
this.href = "/loans/" + loanId + "/transactions/" + transactionId + "?command=interest-refund";
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder updateLoanAvailableDisbursementAmount(final Long loanId) {
|
||||||
|
this.actionName = "UPDATE";
|
||||||
|
this.entityName = "LOAN_AVAILABLE_DISBURSEMENT_AMOUNT";
|
||||||
|
this.entityId = loanId;
|
||||||
|
this.loanId = loanId;
|
||||||
|
this.href = "/loans/" + loanId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7451,7 +7451,7 @@ Feature: Loan
|
|||||||
# --- disbursement - 1 January, 2025 ---
|
# --- disbursement - 1 January, 2025 ---
|
||||||
When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount
|
When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount
|
||||||
When Admin sets the business date to "03 January 2025"
|
When Admin sets the business date to "03 January 2025"
|
||||||
Then Update loan approved amount with new amount "800" value
|
Then Update loan approved amount is forbidden with amount "800" due to higher principal amount on loan
|
||||||
|
|
||||||
@TestRailId:C3868
|
@TestRailId:C3868
|
||||||
Scenario: Verify approved amount change with greater value for progressive multidisbursal loan that expects two tranches - UC7_2
|
Scenario: Verify approved amount change with greater value for progressive multidisbursal loan that expects two tranches - UC7_2
|
||||||
|
|||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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.LoanApprovedAmountWritePlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CommandType(entity = "LOAN_AVAILABLE_DISBURSEMENT_AMOUNT", action = "UPDATE")
|
||||||
|
public class LoanAvailableDisbursementAmountModificationCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LoanApprovedAmountWritePlatformService loanApprovedAmountWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public CommandProcessingResult processCommand(JsonCommand command) {
|
||||||
|
return loanApprovedAmountWritePlatformService.modifyLoanAvailableDisbursementAmount(command.getLoanId(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -23,4 +23,6 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|||||||
public interface LoanApprovedAmountValidator {
|
public interface LoanApprovedAmountValidator {
|
||||||
|
|
||||||
void validateLoanApprovedAmountModification(JsonCommand command);
|
void validateLoanApprovedAmountModification(JsonCommand command);
|
||||||
|
|
||||||
|
void validateLoanAvailableDisbursementAmountModification(JsonCommand command);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -24,4 +24,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|||||||
public interface LoanApprovedAmountWritePlatformService {
|
public interface LoanApprovedAmountWritePlatformService {
|
||||||
|
|
||||||
CommandProcessingResult modifyLoanApprovedAmount(Long loanId, JsonCommand command);
|
CommandProcessingResult modifyLoanApprovedAmount(Long loanId, JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult modifyLoanAvailableDisbursementAmount(Long loanId, JsonCommand command);
|
||||||
}
|
}
|
||||||
|
|||||||
+42
@@ -31,6 +31,7 @@ import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanApprovedAmountHistory;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanApprovedAmountHistory;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanApprovedAmountHistoryRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanApprovedAmountHistoryRepository;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApprovedAmountValidator;
|
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApprovedAmountValidator;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -73,4 +74,45 @@ public class LoanApprovedAmountWritePlatformServiceImpl implements LoanApprovedA
|
|||||||
.with(changes) //
|
.with(changes) //
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult modifyLoanAvailableDisbursementAmount(Long loanId, JsonCommand command) {
|
||||||
|
// API rule validations
|
||||||
|
this.loanApprovedAmountValidator.validateLoanAvailableDisbursementAmountModification(command);
|
||||||
|
|
||||||
|
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||||
|
changes.put("newAvailableDisbursementAmount", command.stringValueOfParameterNamed(LoanApiConstants.amountParameterName));
|
||||||
|
changes.put("locale", command.locale());
|
||||||
|
|
||||||
|
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
|
changes.put("oldApprovedAmount", loan.getApprovedPrincipal());
|
||||||
|
|
||||||
|
BigDecimal expectedDisbursementAmount = loan.getDisbursementDetails().stream().filter(t -> t.actualDisbursementDate() == null)
|
||||||
|
.map(LoanDisbursementDetails::principal).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
|
BigDecimal oldAvailableDisbursement = loan.getApprovedPrincipal().subtract(loan.getSummary().getTotalPrincipal())
|
||||||
|
.subtract(expectedDisbursementAmount);
|
||||||
|
changes.put("oldAvailableDisbursementAmount", oldAvailableDisbursement);
|
||||||
|
|
||||||
|
BigDecimal newAvailableDisbursementAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.amountParameterName);
|
||||||
|
BigDecimal newApprovedAmount = loan.getSummary().getTotalPrincipal().add(expectedDisbursementAmount)
|
||||||
|
.add(newAvailableDisbursementAmount);
|
||||||
|
changes.put("newApprovedAmount", newApprovedAmount);
|
||||||
|
|
||||||
|
LoanApprovedAmountHistory loanApprovedAmountHistory = new LoanApprovedAmountHistory(loan.getId(), newApprovedAmount,
|
||||||
|
loan.getApprovedPrincipal());
|
||||||
|
|
||||||
|
loan.setApprovedPrincipal(newApprovedAmount);
|
||||||
|
loanApprovedAmountHistoryRepository.saveAndFlush(loanApprovedAmountHistory);
|
||||||
|
|
||||||
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanApprovedAmountChangedBusinessEvent(loan));
|
||||||
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||||
|
.withEntityId(loan.getId()) //
|
||||||
|
.withEntityExternalId(loan.getExternalId()) //
|
||||||
|
.withOfficeId(loan.getOfficeId()) //
|
||||||
|
.withClientId(loan.getClientId()) //
|
||||||
|
.withGroupId(loan.getGroupId()) //
|
||||||
|
.with(changes) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
@@ -924,6 +924,34 @@ public class LoansApiResource {
|
|||||||
return getLoanApprovedAmountHistory(null, ExternalIdFactory.produce(loanExternalId));
|
return getLoanApprovedAmountHistory(null, ExternalIdFactory.produce(loanExternalId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("{loanId}/available-disbursement-amount")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(summary = "Modifies the available disbursement amount of the loan", description = "Modifies the available disbursement amount of the loan, this indirectly modifies the approved amount that can be disbursed on the loan")
|
||||||
|
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansAvailableDisbursementAmountRequest.class)))
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansAvailableDisbursementAmountResponse.class))) })
|
||||||
|
public CommandProcessingResult modifyLoanAvailableDisbursementAmount(
|
||||||
|
@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId, @Context final UriInfo uriInfo,
|
||||||
|
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
return modifyLoanAvailableDisbursementAmount(loanId, ExternalId.empty(), apiRequestBodyAsJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("external-id/{loanExternalId}/available-disbursement-amount")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(summary = "Modifies the available disbursement amount of the loan", description = "Modifies the available disbursement amount of the loan, this indirectly modifies the approved amount that can be disbursed on the loan")
|
||||||
|
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansAvailableDisbursementAmountRequest.class)))
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansAvailableDisbursementAmountResponse.class))) })
|
||||||
|
public CommandProcessingResult modifyLoanAvailableDisbursementAmount(
|
||||||
|
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
|
||||||
|
@Context final UriInfo uriInfo, @Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
return modifyLoanAvailableDisbursementAmount(null, ExternalIdFactory.produce(loanExternalId), apiRequestBodyAsJson);
|
||||||
|
}
|
||||||
|
|
||||||
private String retrieveApprovalTemplate(final Long loanId, final String loanExternalIdStr, final String templateType,
|
private String retrieveApprovalTemplate(final Long loanId, final String loanExternalIdStr, final String templateType,
|
||||||
final UriInfo uriInfo) {
|
final UriInfo uriInfo) {
|
||||||
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
@@ -1350,4 +1378,13 @@ public class LoansApiResource {
|
|||||||
return loanApprovedAmountHistoryRepository.findAllByLoanId(resolvedLoanId, sortedByCreationDate);
|
return loanApprovedAmountHistoryRepository.findAllByLoanId(resolvedLoanId, sortedByCreationDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CommandProcessingResult modifyLoanAvailableDisbursementAmount(Long loanId, ExternalId loanExternalId,
|
||||||
|
String apiRequestBodyAsJson) {
|
||||||
|
Long resolvedLoanId = loanId == null ? loanReadPlatformService.getResolvedLoanId(loanExternalId) : loanId;
|
||||||
|
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
||||||
|
CommandWrapper commandRequest = builder.updateLoanAvailableDisbursementAmount(resolvedLoanId).build();
|
||||||
|
|
||||||
|
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+47
@@ -1814,4 +1814,51 @@ final class LoansApiResourceSwagger {
|
|||||||
@Schema(description = "PutLoansApprovedAmountChanges")
|
@Schema(description = "PutLoansApprovedAmountChanges")
|
||||||
public PutLoansApprovedAmountChanges changes;
|
public PutLoansApprovedAmountChanges changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Schema(description = "PutLoansAvailableDisbursementAmountRequest")
|
||||||
|
public static final class PutLoansAvailableDisbursementAmountRequest {
|
||||||
|
|
||||||
|
private PutLoansAvailableDisbursementAmountRequest() {}
|
||||||
|
|
||||||
|
@Schema(example = "1000")
|
||||||
|
public BigDecimal amount;
|
||||||
|
@Schema(example = "en")
|
||||||
|
public String locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "PutLoansAvailableDisbursementAmountResponse")
|
||||||
|
public static final class PutLoansAvailableDisbursementAmountResponse {
|
||||||
|
|
||||||
|
private PutLoansAvailableDisbursementAmountResponse() {}
|
||||||
|
|
||||||
|
static final class PutLoansAvailableDisbursementAmountChanges {
|
||||||
|
|
||||||
|
private PutLoansAvailableDisbursementAmountChanges() {}
|
||||||
|
|
||||||
|
@Schema(example = "1000")
|
||||||
|
public BigDecimal oldApprovedAmount;
|
||||||
|
@Schema(example = "1000")
|
||||||
|
public BigDecimal newApprovedAmount;
|
||||||
|
@Schema(example = "1000")
|
||||||
|
public BigDecimal oldAvailableDisbursementAmount;
|
||||||
|
@Schema(example = "1000")
|
||||||
|
public BigDecimal newAvailableDisbursementAmount;
|
||||||
|
@Schema(example = "en_GB")
|
||||||
|
public String locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(example = "3")
|
||||||
|
public Long resourceId;
|
||||||
|
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
|
||||||
|
public String resourceExternalId;
|
||||||
|
@Schema(example = "2")
|
||||||
|
public Long officeId;
|
||||||
|
@Schema(example = "6")
|
||||||
|
public Long clientId;
|
||||||
|
@Schema(example = "10")
|
||||||
|
public Long groupId;
|
||||||
|
|
||||||
|
@Schema(description = "PutLoansAvailableDisbursementAmountChanges")
|
||||||
|
public PutLoansAvailableDisbursementAmountChanges changes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-1
@@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil;
|
|||||||
import org.apache.fineract.portfolio.common.service.Validator;
|
import org.apache.fineract.portfolio.common.service.Validator;
|
||||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||||
@@ -95,10 +96,64 @@ public final class LoanApprovedAmountValidatorImpl implements LoanApprovedAmount
|
|||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal totalPrincipalOnLoan = loan.getSummary().getTotalPrincipal();
|
BigDecimal totalPrincipalOnLoan = loan.getSummary().getTotalPrincipal();
|
||||||
if (MathUtil.isLessThan(newApprovedAmount, totalPrincipalOnLoan)) {
|
BigDecimal totalExpectedPrincipal = loan.getDisbursementDetails().stream().filter(t -> t.actualDisbursementDate() == null)
|
||||||
|
.map(LoanDisbursementDetails::principal).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
if (MathUtil.isLessThan(newApprovedAmount, totalPrincipalOnLoan.add(totalExpectedPrincipal))) {
|
||||||
baseDataValidator.reset().parameter(LoanApiConstants.amountParameterName)
|
baseDataValidator.reset().parameter(LoanApiConstants.amountParameterName)
|
||||||
.failWithCode("less.than.disbursed.principal.and.capitalized.income");
|
.failWithCode("less.than.disbursed.principal.and.capitalized.income");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateLoanAvailableDisbursementAmountModification(JsonCommand command) {
|
||||||
|
String json = command.json();
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
throw new InvalidJsonException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> supportedParameters = new HashSet<>(
|
||||||
|
Arrays.asList(LoanApiConstants.amountParameterName, LoanApiConstants.localeParameterName));
|
||||||
|
|
||||||
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
|
||||||
|
|
||||||
|
final BigDecimal newAvailableDisbursementAmount = this.fromApiJsonHelper
|
||||||
|
.extractBigDecimalWithLocaleNamed(LoanApiConstants.amountParameterName, element);
|
||||||
|
|
||||||
|
Validator.validateOrThrow("loan.available.disbursement.amount", baseDataValidator -> {
|
||||||
|
baseDataValidator.reset().parameter(LoanApiConstants.amountParameterName).value(newAvailableDisbursementAmount).notNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
Validator.validateOrThrowDomainViolation("loan.available.disbursement.amount", baseDataValidator -> {
|
||||||
|
baseDataValidator.reset().parameter(LoanApiConstants.amountParameterName).value(newAvailableDisbursementAmount)
|
||||||
|
.zeroOrPositiveAmount();
|
||||||
|
|
||||||
|
final Long loanId = command.getLoanId();
|
||||||
|
Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
|
||||||
|
|
||||||
|
if (INVALID_LOAN_STATUSES_FOR_APPROVED_AMOUNT_MODIFICATION.contains(loan.getStatus())) {
|
||||||
|
baseDataValidator.reset()
|
||||||
|
.failWithCodeNoParameterAddedToErrorCode("loan.status.not.valid.for.available.disbursement.amount.modification");
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal maximumThresholdForApprovedAmount;
|
||||||
|
if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||||
|
maximumThresholdForApprovedAmount = loanApplicationValidator.getOverAppliedMax(loan);
|
||||||
|
} else {
|
||||||
|
maximumThresholdForApprovedAmount = loan.getProposedPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal expectedDisbursementAmount = loan.getDisbursementDetails().stream().filter(t -> t.actualDisbursementDate() == null)
|
||||||
|
.map(LoanDisbursementDetails::principal).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
|
BigDecimal maximumAvailableDisbursementThreshold = maximumThresholdForApprovedAmount
|
||||||
|
.subtract(loan.getSummary().getTotalPrincipal()).subtract(expectedDisbursementAmount);
|
||||||
|
if (MathUtil.isGreaterThan(newAvailableDisbursementAmount, maximumAvailableDisbursementThreshold)) {
|
||||||
|
baseDataValidator.reset().parameter(LoanApiConstants.amountParameterName)
|
||||||
|
.failWithCode("can't.be.greater.than.maximum.available.disbursement.amount.calculation");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -84,6 +84,8 @@ import org.apache.fineract.client.models.PostLoansResponse;
|
|||||||
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||||
import org.apache.fineract.client.models.PutLoansApprovedAmountRequest;
|
import org.apache.fineract.client.models.PutLoansApprovedAmountRequest;
|
||||||
import org.apache.fineract.client.models.PutLoansApprovedAmountResponse;
|
import org.apache.fineract.client.models.PutLoansApprovedAmountResponse;
|
||||||
|
import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountRequest;
|
||||||
|
import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountResponse;
|
||||||
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
|
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.RetrieveLoansPointInTimeRequest;
|
import org.apache.fineract.client.models.RetrieveLoansPointInTimeRequest;
|
||||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||||
@@ -789,6 +791,12 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
|||||||
return Calls.ok(fineractClient().loans.getLoanApprovedAmountHistory(loanId));
|
return Calls.ok(fineractClient().loans.getLoanApprovedAmountHistory(loanId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PutLoansAvailableDisbursementAmountResponse modifyLoanAvailableDisbursementAmount(Long loanId, BigDecimal approvedAmount) {
|
||||||
|
PutLoansAvailableDisbursementAmountRequest request = new PutLoansAvailableDisbursementAmountRequest().amount(approvedAmount)
|
||||||
|
.locale("en");
|
||||||
|
return Calls.ok(fineractClient().loans.modifyLoanAvailableDisbursementAmount(loanId, request));
|
||||||
|
}
|
||||||
|
|
||||||
protected void verifyOutstanding(LoanPointInTimeData loan, OutstandingAmounts outstanding) {
|
protected void verifyOutstanding(LoanPointInTimeData loan, OutstandingAmounts outstanding) {
|
||||||
assertThat(BigDecimal.valueOf(outstanding.principalOutstanding))
|
assertThat(BigDecimal.valueOf(outstanding.principalOutstanding))
|
||||||
.isEqualByComparingTo(loan.getPrincipal().getPrincipalOutstanding());
|
.isEqualByComparingTo(loan.getPrincipal().getPrincipalOutstanding());
|
||||||
|
|||||||
+221
-1
@@ -36,13 +36,14 @@ import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
|||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansResponse;
|
import org.apache.fineract.client.models.PostLoansResponse;
|
||||||
import org.apache.fineract.client.models.PutLoansApprovedAmountResponse;
|
import org.apache.fineract.client.models.PutLoansApprovedAmountResponse;
|
||||||
|
import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountResponse;
|
||||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
import org.apache.fineract.integrationtests.common.externalevents.LoanBusinessEvent;
|
import org.apache.fineract.integrationtests.common.externalevents.LoanBusinessEvent;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class LoanModifyApprovedAmountTests extends BaseLoanIntegrationTest {
|
public class LoanModifyApprovedAmountTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidLoanApprovedAmountModification() {
|
public void testValidLoanApprovedAmountModification() {
|
||||||
@@ -478,4 +479,223 @@ public class LoanModifyApprovedAmountTests extends BaseLoanIntegrationTest {
|
|||||||
assertTrue(exception.getMessage().contains("amount.can't.be.greater.than.maximum.applied.loan.amount.calculation"));
|
assertTrue(exception.getMessage().contains("amount.can't.be.greater.than.maximum.applied.loan.amount.calculation"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidLoanAvailableDisbursementAmountModification() {
|
||||||
|
BigDecimal sixHundred = BigDecimal.valueOf(600.0);
|
||||||
|
BigDecimal sevenHundred = BigDecimal.valueOf(700.0);
|
||||||
|
BigDecimal nineHundred = BigDecimal.valueOf(900.0);
|
||||||
|
BigDecimal thousand = BigDecimal.valueOf(1000.0);
|
||||||
|
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
1000.0, 10.0, 4, null);
|
||||||
|
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
|
||||||
|
PutLoansAvailableDisbursementAmountResponse putLoansAvailableDisbursementAmountResponse = modifyLoanAvailableDisbursementAmount(
|
||||||
|
loanId, sixHundred);
|
||||||
|
|
||||||
|
Assertions.assertEquals(loanId, putLoansAvailableDisbursementAmountResponse.getResourceId());
|
||||||
|
Assertions.assertNotNull(putLoansAvailableDisbursementAmountResponse.getChanges());
|
||||||
|
Assertions.assertNotNull(putLoansAvailableDisbursementAmountResponse.getChanges().getNewApprovedAmount());
|
||||||
|
Assertions.assertNotNull(putLoansAvailableDisbursementAmountResponse.getChanges().getOldApprovedAmount());
|
||||||
|
Assertions.assertNotNull(putLoansAvailableDisbursementAmountResponse.getChanges().getOldAvailableDisbursementAmount());
|
||||||
|
Assertions.assertNotNull(putLoansAvailableDisbursementAmountResponse.getChanges().getNewAvailableDisbursementAmount());
|
||||||
|
Assertions.assertEquals(sevenHundred,
|
||||||
|
putLoansAvailableDisbursementAmountResponse.getChanges().getNewApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
Assertions.assertEquals(thousand,
|
||||||
|
putLoansAvailableDisbursementAmountResponse.getChanges().getOldApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
Assertions.assertEquals(sixHundred, putLoansAvailableDisbursementAmountResponse.getChanges().getNewAvailableDisbursementAmount()
|
||||||
|
.setScale(1, RoundingMode.HALF_UP));
|
||||||
|
Assertions.assertEquals(nineHundred, putLoansAvailableDisbursementAmountResponse.getChanges()
|
||||||
|
.getOldAvailableDisbursementAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoanAvailableDisbursementAmountModificationEvent() {
|
||||||
|
externalEventHelper.enableBusinessEvent("LoanApprovedAmountChangedBusinessEvent");
|
||||||
|
BigDecimal sixHundred = BigDecimal.valueOf(600.0);
|
||||||
|
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
1000.0, 10.0, 4, null);
|
||||||
|
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
|
||||||
|
|
||||||
|
deleteAllExternalEvents();
|
||||||
|
modifyLoanAvailableDisbursementAmount(loanId, sixHundred);
|
||||||
|
|
||||||
|
verifyBusinessEvents(new LoanBusinessEvent("LoanApprovedAmountChangedBusinessEvent", "01 January 2024", 300, 100.0, 100.0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidLoanAvailableDisbursementAmountModificationInvalidRequest() {
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||||
|
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
1000.0, 10.0, 4, null);
|
||||||
|
|
||||||
|
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
|
||||||
|
() -> modifyLoanAvailableDisbursementAmount(loanId, null));
|
||||||
|
|
||||||
|
assertEquals(400, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("validation.msg.loan.available.disbursement.amount.amount.cannot.be.blank"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidLoanAvailableDisbursementAmountModificationInvalidLoanStatus() {
|
||||||
|
BigDecimal sixHundred = BigDecimal.valueOf(600.0);
|
||||||
|
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
|
||||||
|
loanProductsResponse.getResourceId(), "1 January 2024", 1000.0, 10.0, 4, null));
|
||||||
|
|
||||||
|
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
|
||||||
|
() -> modifyLoanAvailableDisbursementAmount(postLoansResponse.getResourceId(), sixHundred));
|
||||||
|
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains(
|
||||||
|
"validation.msg.loan.available.disbursement.amount.loan.status.not.valid.for.available.disbursement.amount.modification"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyLoanAvailableDisbursementAmountHigherThanApprovedAmount() {
|
||||||
|
BigDecimal twoThousand = BigDecimal.valueOf(2000.0);
|
||||||
|
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
1000.0, 10.0, 4, null);
|
||||||
|
|
||||||
|
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
|
||||||
|
() -> modifyLoanAvailableDisbursementAmount(loanId, twoThousand));
|
||||||
|
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains(
|
||||||
|
"validation.msg.loan.available.disbursement.amount.amount.can't.be.greater.than.maximum.available.disbursement.amount.calculation"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyLoanAvailableDisbursementAmountWithNegativeAmount() {
|
||||||
|
BigDecimal sixHundred = BigDecimal.valueOf(600.0);
|
||||||
|
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
1000.0, 10.0, 4, null);
|
||||||
|
|
||||||
|
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
|
||||||
|
() -> modifyLoanAvailableDisbursementAmount(loanId, sixHundred.negate()));
|
||||||
|
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("validation.msg.loan.available.disbursement.amount.amount.not.zero.or.greater"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyLoanAvailableDisbursementAmountCapitalizedIncomeCountsAsPrincipal() {
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressiveWithCapitalizedIncome());
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
1000.0, 10.0, 4, null);
|
||||||
|
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(500), "1 January 2024");
|
||||||
|
PostLoansLoanIdTransactionsResponse capitalizedIncomeResponse = loanTransactionHelper.addCapitalizedIncome(loanId,
|
||||||
|
"1 January 2024", 500.0);
|
||||||
|
|
||||||
|
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
|
||||||
|
() -> modifyLoanAvailableDisbursementAmount(loanId, BigDecimal.valueOf(600.0)));
|
||||||
|
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains(
|
||||||
|
"validation.msg.loan.available.disbursement.amount.amount.can't.be.greater.than.maximum.available.disbursement.amount.calculation"));
|
||||||
|
|
||||||
|
loanTransactionHelper.reverseLoanTransaction(capitalizedIncomeResponse.getLoanId(), capitalizedIncomeResponse.getResourceId(),
|
||||||
|
"1 January 2024");
|
||||||
|
|
||||||
|
Assertions.assertDoesNotThrow(() -> modifyLoanAvailableDisbursementAmount(loanId, BigDecimal.valueOf(600.0)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyLoanAvailableDisbursementAmountFutureExpectedDisbursementsCountAsPrincipal() {
|
||||||
|
BigDecimal twoHundred = BigDecimal.valueOf(200.0);
|
||||||
|
BigDecimal eightHundred = BigDecimal.valueOf(800.0);
|
||||||
|
BigDecimal thousand = BigDecimal.valueOf(1000.0);
|
||||||
|
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().disallowExpectedDisbursements(false).allowApprovedDisbursedAmountsOverApplied(null)
|
||||||
|
.overAppliedCalculationType(null).overAppliedNumber(null));
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
1000.0, 7.0, 6, (request) -> request.disbursementData(List.of(new PostLoansDisbursementData()
|
||||||
|
.expectedDisbursementDate("1 January 2024").principal(BigDecimal.valueOf(800.0)))));
|
||||||
|
|
||||||
|
PutLoansAvailableDisbursementAmountResponse putLoansAvailableDisbursementAmountResponse = modifyLoanAvailableDisbursementAmount(
|
||||||
|
loanId, BigDecimal.ZERO);
|
||||||
|
|
||||||
|
Assertions.assertEquals(eightHundred,
|
||||||
|
putLoansAvailableDisbursementAmountResponse.getChanges().getNewApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
Assertions.assertEquals(thousand,
|
||||||
|
putLoansAvailableDisbursementAmountResponse.getChanges().getOldApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
Assertions.assertEquals(BigDecimal.ZERO,
|
||||||
|
putLoansAvailableDisbursementAmountResponse.getChanges().getNewAvailableDisbursementAmount());
|
||||||
|
Assertions.assertEquals(twoHundred, putLoansAvailableDisbursementAmountResponse.getChanges().getOldAvailableDisbursementAmount()
|
||||||
|
.setScale(1, RoundingMode.HALF_UP));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyLoanAvailableDisbursementAmountCreatesHistoryEntries() {
|
||||||
|
BigDecimal fourHundred = BigDecimal.valueOf(400.0);
|
||||||
|
BigDecimal sixHundred = BigDecimal.valueOf(600.0);
|
||||||
|
BigDecimal eightHundred = BigDecimal.valueOf(800.0);
|
||||||
|
BigDecimal thousand = BigDecimal.valueOf(1000.0);
|
||||||
|
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
1000.0, 10.0, 4, null);
|
||||||
|
|
||||||
|
modifyLoanAvailableDisbursementAmount(loanId, BigDecimal.valueOf(800.0));
|
||||||
|
modifyLoanAvailableDisbursementAmount(loanId, BigDecimal.valueOf(600.0));
|
||||||
|
modifyLoanAvailableDisbursementAmount(loanId, BigDecimal.valueOf(400.0));
|
||||||
|
|
||||||
|
List<LoanApprovedAmountHistoryData> loanApprovedAmountHistory = getLoanApprovedAmountHistory(loanId);
|
||||||
|
|
||||||
|
Assertions.assertNotNull(loanApprovedAmountHistory);
|
||||||
|
Assertions.assertEquals(3, loanApprovedAmountHistory.size());
|
||||||
|
|
||||||
|
Assertions.assertEquals(thousand, loanApprovedAmountHistory.get(0).getOldApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
Assertions.assertEquals(eightHundred,
|
||||||
|
loanApprovedAmountHistory.get(0).getNewApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
|
||||||
|
Assertions.assertEquals(eightHundred,
|
||||||
|
loanApprovedAmountHistory.get(1).getOldApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
Assertions.assertEquals(sixHundred, loanApprovedAmountHistory.get(1).getNewApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
|
||||||
|
Assertions.assertEquals(sixHundred, loanApprovedAmountHistory.get(2).getOldApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
Assertions.assertEquals(fourHundred, loanApprovedAmountHistory.get(2).getNewApprovedAmount().setScale(1, RoundingMode.HALF_UP));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user