Setting Fraud flag for a loan account

This commit is contained in:
Jose Alberto Hernandez
2022-10-21 20:26:21 +02:00
committed by Arnold Gálovics
parent 0049a9e3f9
commit b2482c9226
17 changed files with 403 additions and 49 deletions
@@ -994,6 +994,15 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder markAsFraud(final Long loanId) {
this.actionName = "SETFRAUD";
this.entityName = "LOAN";
this.entityId = loanId;
this.loanId = loanId;
this.href = "/loans/" + loanId;
return this;
}
public CommandWrapperBuilder updateDisbusementDate(final Long loanId, final Long disbursementId) {
this.actionName = "UPDATE";
this.entityName = "DISBURSEMENTDETAIL";
@@ -161,7 +161,12 @@ public interface LoanApiConstants {
String TRANSACTION_AMOUNT_PARAMNAME = "transactionAmount";
String PAYMENT_TYPE_PARAMNAME = "paymentTypeId";
String FRAUD_ATTRIBUTE_NAME = "fraud";
// Commands
String CHARGEBACK_TRANSACTION_COMMAND = "chargeback";
String MARK_AS_FRAUD_COMMAND = "markAsFraud";
// Data Validator names
String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud";
}
@@ -55,7 +55,6 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -75,6 +74,7 @@ import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamE
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant;
@@ -759,7 +759,7 @@ public class LoansApiResource {
@QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
if (is(commandParam, "calculateLoanSchedule")) {
if (CommandParameterUtil.is(commandParam, "calculateLoanSchedule")) {
final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
@@ -786,13 +786,18 @@ public class LoansApiResource {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansLoanIdResponse.class))) })
public String modifyLoanApplication(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateLoanApplication(loanId).withJson(apiRequestBodyAsJson)
.build();
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
CommandWrapper commandRequest = null;
if (CommandParameterUtil.is(commandParam, LoanApiConstants.MARK_AS_FRAUD_COMMAND)) {
commandRequest = builder.markAsFraud(loanId).build();
} else {
commandRequest = builder.updateLoanApplication(loanId).build();
}
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@@ -837,29 +842,29 @@ public class LoansApiResource {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
CommandWrapper commandRequest = null;
if (is(commandParam, "reject")) {
if (CommandParameterUtil.is(commandParam, "reject")) {
commandRequest = builder.rejectLoanApplication(loanId).build();
} else if (is(commandParam, "withdrawnByApplicant")) {
} else if (CommandParameterUtil.is(commandParam, "withdrawnByApplicant")) {
commandRequest = builder.withdrawLoanApplication(loanId).build();
} else if (is(commandParam, "approve")) {
} else if (CommandParameterUtil.is(commandParam, "approve")) {
commandRequest = builder.approveLoanApplication(loanId).build();
} else if (is(commandParam, "disburse")) {
} else if (CommandParameterUtil.is(commandParam, "disburse")) {
commandRequest = builder.disburseLoanApplication(loanId).build();
} else if (is(commandParam, "disburseToSavings")) {
} else if (CommandParameterUtil.is(commandParam, "disburseToSavings")) {
commandRequest = builder.disburseLoanToSavingsApplication(loanId).build();
} else if (is(commandParam, "undoapproval")) {
} else if (CommandParameterUtil.is(commandParam, "undoapproval")) {
commandRequest = builder.undoLoanApplicationApproval(loanId).build();
} else if (is(commandParam, "undodisbursal")) {
} else if (CommandParameterUtil.is(commandParam, "undodisbursal")) {
commandRequest = builder.undoLoanApplicationDisbursal(loanId).build();
} else if (is(commandParam, "undolastdisbursal")) {
} else if (CommandParameterUtil.is(commandParam, "undolastdisbursal")) {
commandRequest = builder.undoLastDisbursalLoanApplication(loanId).build();
} else if (is(commandParam, "assignloanofficer")) {
} else if (CommandParameterUtil.is(commandParam, "assignloanofficer")) {
commandRequest = builder.assignLoanOfficer(loanId).build();
} else if (is(commandParam, "unassignloanofficer")) {
} else if (CommandParameterUtil.is(commandParam, "unassignloanofficer")) {
commandRequest = builder.unassignLoanOfficer(loanId).build();
} else if (is(commandParam, "recoverGuarantees")) {
} else if (CommandParameterUtil.is(commandParam, "recoverGuarantees")) {
commandRequest = new CommandWrapperBuilder().recoverFromGuarantor(loanId).build();
} else if (is(commandParam, "assigndelinquency")) {
} else if (CommandParameterUtil.is(commandParam, "assigndelinquency")) {
commandRequest = builder.assignDelinquency(loanId).build();
}
@@ -871,10 +876,6 @@ public class LoansApiResource {
return this.toApiJsonSerializer.serialize(result);
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
@GET
@Path("downloadtemplate")
@Produces("application/vnd.ms-excel")
@@ -915,17 +916,17 @@ public class LoansApiResource {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
CommandWrapper commandRequest = null;
if (is(commandParam, "reject")) {
if (CommandParameterUtil.is(commandParam, "reject")) {
commandRequest = builder.rejectGLIMApplication(glimId).build();
} else if (is(commandParam, "approve")) {
} else if (CommandParameterUtil.is(commandParam, "approve")) {
commandRequest = builder.approveGLIMLoanApplication(glimId).build();
} else if (is(commandParam, "disburse")) {
} else if (CommandParameterUtil.is(commandParam, "disburse")) {
commandRequest = builder.disburseGlimLoanApplication(glimId).build();
} else if (is(commandParam, "glimrepayment")) {
} else if (CommandParameterUtil.is(commandParam, "glimrepayment")) {
commandRequest = builder.repaymentGlimLoanApplication(glimId).build();
} else if (is(commandParam, "undodisbursal")) {
} else if (CommandParameterUtil.is(commandParam, "undodisbursal")) {
commandRequest = builder.undoGLIMLoanDisbursal(glimId).build();
} else if (is(commandParam, "undoapproval")) {
} else if (CommandParameterUtil.is(commandParam, "undoapproval")) {
commandRequest = builder.undoGLIMLoanApproval(glimId).build();
}
@@ -972,6 +972,8 @@ final class LoansApiResourceSwagger {
@Schema(description = "Set of charges")
public Set<GetLoansLoanIdLoanChargeData> charges;
public GetDelinquencyRangesResponse delinquencyRange;
@Schema(example = "false")
public Boolean fraud;
}
@Schema(description = "GetLoansResponse")
@@ -1169,6 +1171,8 @@ final class LoansApiResourceSwagger {
public String interestChargedFromDate;
@Schema(example = "true")
public Boolean isEqualAmortization;
@Schema(example = "true")
public Boolean fraud;
@Schema(example = "1")
public Integer graceOnArrearsAgeing;
@Schema(example = "1")
@@ -189,6 +189,7 @@ public final class LoanAccountData {
private final Collection<LoanAccountSummaryData> clientActiveLoanOptions;
private final Boolean canUseForTopup;
private final boolean isTopup;
private final boolean fraud;
private final Long closureLoanId;
private final String closureLoanAccountNo;
private final BigDecimal topupAmount;
@@ -433,6 +434,7 @@ public final class LoanAccountData {
this.delinquent = null;
this.delinquencyRange = null;
this.disallowExpectedDisbursements = false;
this.fraud = false;
}
public static final Comparator<LoanAccountData> ClientNameComparator = new Comparator<LoanAccountData>() {
@@ -570,6 +572,7 @@ public final class LoanAccountData {
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
final Boolean disallowExpectedDisbursements = false;
final boolean fraud = false;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId,
clientExternalId, group, loanType, loanProductId, loanProductName, loanProductDescription,
@@ -592,7 +595,7 @@ public final class LoanAccountData {
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount,
isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange,
disallowExpectedDisbursements);
disallowExpectedDisbursements, fraud);
}
/**
@@ -719,6 +722,7 @@ public final class LoanAccountData {
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
final Boolean disallowExpectedDisbursements = false;
final boolean fraud = false;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId,
clientExternalId, group, loanType, loanProductId, loanProductName, loanProductDescription,
@@ -741,7 +745,7 @@ public final class LoanAccountData {
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount,
isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange,
disallowExpectedDisbursements);
disallowExpectedDisbursements, fraud);
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
@@ -772,7 +776,8 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements,
acc.fraud);
}
/**
@@ -902,6 +907,7 @@ public final class LoanAccountData {
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
final Boolean disallowExpectedDisbursements = false;
final Boolean fraud = false;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId,
clientExternalId, group, loanType, loanProductId, loanProductName, loanProductDescription,
@@ -924,7 +930,7 @@ public final class LoanAccountData {
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount,
isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange,
disallowExpectedDisbursements);
disallowExpectedDisbursements, fraud);
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
@@ -955,7 +961,8 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements,
acc.fraud);
}
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
@@ -1083,6 +1090,7 @@ public final class LoanAccountData {
final Boolean isRatesEnabled = false;
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
final boolean fraud = false;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId,
clientExternalId, group, loanType, product.getId(), product.getName(), product.getDescription(),
@@ -1109,7 +1117,7 @@ public final class LoanAccountData {
interestRatesPeriods, product.isAllowVariableInstallments(), product.getMinimumGap(), product.getMaximumGap(), subStatus,
canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount,
product.isEqualAmortization(), rates, isRatesEnabled, product.getFixedPrincipalPercentagePerInstallment(), delinquent,
delinquencyRange, product.getDisallowExpectedDisbursements());
delinquencyRange, product.getDisallowExpectedDisbursements(), fraud);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -1180,7 +1188,7 @@ public final class LoanAccountData {
product.isAllowVariableInstallments(), product.getMinimumGap(), product.getMaximumGap(), acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount,
product.isEqualAmortization(), acc.rates, acc.isRatesEnabled, product.getFixedPrincipalPercentagePerInstallment(),
delinquent, delinquencyRange, product.getDisallowExpectedDisbursements());
delinquent, delinquencyRange, product.getDisallowExpectedDisbursements(), acc.fraud);
}
/*
@@ -1212,7 +1220,7 @@ public final class LoanAccountData {
final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus,
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 DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud) {
final LoanScheduleData repaymentSchedule = null;
final Collection<LoanTransactionData> transactions = null;
@@ -1274,7 +1282,7 @@ public final class LoanAccountData {
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount,
isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange,
disallowExpectedDisbursements);
disallowExpectedDisbursements, fraud);
}
/*
@@ -1324,7 +1332,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, clientActiveLoanOptions, acc.isTopup, acc.closureLoanId,
acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, rates, isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements);
acc.fixedPrincipalPercentagePerInstallment, delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements, acc.fraud);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
@@ -1367,7 +1375,8 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements,
acc.fraud);
}
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
@@ -1433,7 +1442,8 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements,
acc.fraud);
}
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData,
@@ -1467,7 +1477,8 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements,
acc.fraud);
}
public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
@@ -1496,7 +1507,8 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements,
acc.fraud);
}
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
@@ -1526,7 +1538,8 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange, acc.disallowExpectedDisbursements,
acc.fraud);
}
private LoanAccountData(final Long id, //
@@ -1581,7 +1594,7 @@ public final class LoanAccountData {
final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final boolean isTopup, final Long closureLoanId,
final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization, final List<RateData> rates,
final Boolean isRatesEnabled, final BigDecimal fixedPrincipalPercentagePerInstallment, final CollectionData delinquent,
final DelinquencyRangeData delinquencyRange, final Boolean disallowExpectedDisbursements) {
final DelinquencyRangeData delinquencyRange, final Boolean disallowExpectedDisbursements, final Boolean fraud) {
this.id = id;
this.accountNo = accountNo;
@@ -1772,6 +1785,7 @@ public final class LoanAccountData {
this.delinquent = delinquent;
this.delinquencyRange = delinquencyRange;
this.disallowExpectedDisbursements = disallowExpectedDisbursements;
this.fraud = fraud;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
@@ -1910,4 +1924,5 @@ public final class LoanAccountData {
public boolean isIsTopup() {
return isTopup;
}
}
@@ -416,6 +416,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "is_topup", nullable = false)
private boolean isTopup = false;
@Column(name = "is_fraud", nullable = false)
private boolean fraud = false;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
private LoanTopupDetails loanTopupDetails;
@@ -6711,6 +6714,14 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return this.isTopup;
}
public void markAsFraud(final boolean value) {
this.fraud = value;
}
public boolean isFraud() {
return this.fraud;
}
public BigDecimal getFirstDisbursalAmount() {
BigDecimal firstDisbursalAmount;
@@ -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 = "SETFRAUD")
public class MarkLoanAsFraudCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.markLoanAsFraud(command.getLoanId(), command);
}
}
@@ -95,7 +95,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
LoanApiConstants.applicationId, // glim specific
LoanApiConstants.lastApplication, // glim specific
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS));
LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanApiConstants.FRAUD_ATTRIBUTE_NAME));
public static final String LOANAPPLICATION_UNDO = "loanapplication.undo";
private final FromJsonHelper fromApiJsonHelper;
@@ -518,6 +518,27 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateMarkAsFraudLoan(final String json) {
if (StringUtils.isBlank(json)) {
return;
}
Set<String> transactionParameters = new HashSet<>(Arrays.asList(LoanApiConstants.FRAUD_ATTRIBUTE_NAME));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(LoanApiConstants.LOAN_FRAUD_DATAVALIDATOR_PREFIX);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final boolean isFraud = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.FRAUD_ATTRIBUTE_NAME, element);
baseDataValidator.reset().parameter(LoanApiConstants.FRAUD_ATTRIBUTE_NAME).value(isFraud).notNull();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateInstallmentChargeTransaction(final String json) {
if (StringUtils.isBlank(json)) {
@@ -629,14 +629,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " l.penalty_charges_repaid_derived as penaltyChargesPaid,"
+ " l.penalty_charges_waived_derived as penaltyChargesWaived,"
+ " l.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff,"
+ " l.penalty_charges_outstanding_derived as penaltyChargesOutstanding,"
+ " l.penalty_charges_outstanding_derived as penaltyChargesOutstanding, "
+ " l.total_expected_repayment_derived as totalExpectedRepayment, l.total_repayment_derived as totalRepayment,"
+ " l.total_expected_costofloan_derived as totalExpectedCostOfLoan, l.total_costofloan_derived as totalCostOfLoan,"
+ " l.total_waived_derived as totalWaived, l.total_writtenoff_derived as totalWrittenOff,"
+ " l.writeoff_reason_cv_id as writeoffReasonId, codev.code_value as writeoffReason,"
+ " l.total_outstanding_derived as totalOutstanding, l.total_overpaid_derived as totalOverpaid,"
+ " l.fixed_emi_amount as fixedEmiAmount, l.max_outstanding_loan_balance as outstandingLoanBalance,"
+ " l.loan_sub_status_id as loanSubStatusId, la.principal_overdue_derived as principalOverdue,"
+ " l.loan_sub_status_id as loanSubStatusId, la.principal_overdue_derived as principalOverdue, l.is_fraud as isFraud, "
+ " la.interest_overdue_derived as interestOverdue, la.fee_charges_overdue_derived as feeChargesOverdue,"
+ " la.penalty_charges_overdue_derived as penaltyChargesOverdue, la.total_overdue_derived as totalOverdue,"
+ " la.overdue_since_date_derived as overdueSinceDate,"
@@ -989,6 +989,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
// Current Delinquency Range Data
DelinquencyRangeData delinquencyRange = this.delinquencyReadPlatformService.retrieveCurrentDelinquencyTag(id);
final boolean isFraud = rs.getBoolean("isFraud");
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName,
clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription,
isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -1003,7 +1005,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData,
createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, maximumGap, loanSubStatus,
canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization,
fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements);
fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud);
}
}
@@ -119,4 +119,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand command);
CommandProcessingResult markLoanAsFraud(Long loanId, JsonCommand command);
}
@@ -3303,6 +3303,37 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
@Override
@Transactional
public CommandProcessingResult markLoanAsFraud(Long loanId, JsonCommand command) {
this.loanEventApiJsonValidator.validateMarkAsFraudLoan(command.json());
Loan loan = this.loanAssembler.assembleFrom(loanId);
final Map<String, Object> changes = new LinkedHashMap<>();
if (!loan.isOpen()) {
throw new PlatformServiceUnavailableException("error.msg.loan.mark.as.fraud.not.allowed",
"Loan Id: " + loanId + " mark as fraud is not allowed as loan status is not active", loan.getStatus().getCode());
}
final boolean fraud = command.booleanPrimitiveValueOfParameterNamed(LoanApiConstants.FRAUD_ATTRIBUTE_NAME);
if (loan.isFraud() != fraud) {
loan.markAsFraud(fraud);
this.loanRepository.save(loan);
changes.put(LoanApiConstants.FRAUD_ATTRIBUTE_NAME, fraud);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loan.getId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes) //
.build();
}
@Override
@Transactional
public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) {
@@ -243,8 +243,8 @@ public class SelfLoansApiResource {
if (clientId != null) {
validateAppuserClientsMapping(clientId);
}
return this.loansApiResource.modifyLoanApplication(loanId, apiRequestBodyAsJson);
final String command = null;
return this.loansApiResource.modifyLoanApplication(loanId, command, apiRequestBodyAsJson);
}
@POST
@@ -81,4 +81,5 @@
<include file="parts/0059_add_spring_batch_loan_id_list_table.xml" relativeToChangelogFile="true"/>
<include file="parts/0060_add_job_name_to_command_source.xml" relativeToChangelogFile="true"/>
<include file="parts/0061_add_idempotency_key_to_command_source.xml" relativeToChangelogFile="true"/>
<include file="parts/0062_add_fraud_attribute_to_loan.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<addColumn tableName="m_loan">
<column defaultValueBoolean="false" name="is_fraud" type="boolean">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="2">
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="SETFRAUD_LOAN"/>
<column name="entity_name" value="LOAN"/>
<column name="action_name" value="SETFRAUD"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,157 @@
/**
* 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.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate;
import java.util.HashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@Slf4j
public class LoanAccountFraudTest {
private ResponseSpecification responseSpec;
private ResponseSpecification responseSpecError;
private RequestSpecification requestSpec;
private LoanTransactionHelper loanTransactionHelper;
private final String amountVal = "1000";
private LocalDate todaysDate;
private String operationDate;
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.responseSpecError = new ResponseSpecBuilder().expectStatusCode(503).build();
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
this.todaysDate = Utils.getLocalDateOfTenant();
this.operationDate = Utils.dateFormatter.format(this.todaysDate);
}
@Test
public void testMarkLoanAsFraud() {
final String command = "markAsFraud";
// Client and Loan account creation
final Integer loanId = createAccounts(15, 1);
GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
// Default values Not Null and False
assertNotNull(getLoansLoanIdResponse.getFraud());
assertEquals(Boolean.FALSE, getLoansLoanIdResponse.getFraud());
String payload = loanTransactionHelper.getLoanFraudPayloadAsJSON("fraud", "true");
// Send the request expecting an error because the Loan is not active yet
PutLoansLoanIdResponse putLoansLoanIdResponse = loanTransactionHelper.modifyLoanCommand(loanId, command, payload,
this.responseSpecError);
String statusCode = getLoansLoanIdResponse.getStatus().getCode();
log.info("Loan with Id {} is with Status {}", getLoansLoanIdResponse.getId(), statusCode);
// Approve the Loan active
approveAndDisburseLoan(loanId, this.operationDate, this.amountVal);
// Default values Not Null and False
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
assertNotNull(getLoansLoanIdResponse.getFraud());
assertEquals(Boolean.FALSE, getLoansLoanIdResponse.getFraud());
statusCode = getLoansLoanIdResponse.getStatus().getCode();
log.info("Loan with Id {} is with Status {}", getLoansLoanIdResponse.getId(), statusCode);
// Mark On the Fraud
putLoansLoanIdResponse = loanTransactionHelper.modifyLoanCommand(loanId, command, payload, this.responseSpec);
assertNotNull(putLoansLoanIdResponse);
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
assertNotNull(getLoansLoanIdResponse.getFraud());
assertEquals(Boolean.TRUE, getLoansLoanIdResponse.getFraud());
// Mark Off the Fraud
payload = loanTransactionHelper.getLoanFraudPayloadAsJSON("fraud", "false");
putLoansLoanIdResponse = loanTransactionHelper.modifyLoanCommand(loanId, command, payload, this.responseSpec);
assertNotNull(putLoansLoanIdResponse);
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
assertNotNull(getLoansLoanIdResponse.getFraud());
assertEquals(Boolean.FALSE, getLoansLoanIdResponse.getFraud());
}
private Integer createAccounts(final Integer daysToSubtract, final Integer numberOfRepayments) {
// Client and Loan account creation
final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper, null);
// Older date to have more than one overdue installment
final LocalDate transactionDate = this.todaysDate.minusDays(daysToSubtract + (30 * (numberOfRepayments - 1)));
String operationDate = Utils.dateFormatter.format(transactionDate);
return createLoanAccount(loanTransactionHelper, clientId.toString(), getLoanProductsProductResponse.getId().toString(),
operationDate, amountVal, numberOfRepayments.toString());
}
private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
final Integer delinquencyBucketId) {
final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);
final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
return loanTransactionHelper.getLoanProduct(loanProductId);
}
private Integer createLoanAccount(final LoanTransactionHelper loanTransactionHelper, final String clientId, final String loanProductId,
final String operationDate, final String principalAmount, final String numberOfRepayments) {
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principalAmount)
.withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsMonths().withNumberOfRepayments(numberOfRepayments)
.withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() //
.withInterestRatePerPeriod("0") //
.withExpectedDisbursementDate(operationDate) //
.withInterestTypeAsDecliningBalance() //
.withSubmittedOnDate(operationDate) //
.build(clientId, loanProductId, null);
return loanTransactionHelper.getLoanId(loanApplicationJSON);
}
private void approveAndDisburseLoan(final Integer loanId, final String operationDate, final String principalAmount) {
loanTransactionHelper.approveLoan(operationDate, principalAmount, loanId, null);
loanTransactionHelper.disburseLoanWithNetDisbursalAmount(operationDate, loanId, principalAmount);
}
}
@@ -147,6 +147,13 @@ public class LoanTransactionHelper {
return GSON.fromJson(response, PutLoansLoanIdResponse.class);
}
public PutLoansLoanIdResponse modifyLoanCommand(final Integer loanId, final String command, final String payload,
ResponseSpecification responseSpec) {
final String url = "/fineract-provider/api/v1/loans/" + loanId + "?" + Utils.TENANT_IDENTIFIER + "&command=" + command;
final String response = Utils.performServerPut(this.requestSpec, responseSpec, url, payload, null);
return GSON.fromJson(response, PutLoansLoanIdResponse.class);
}
public ArrayList getLoanRepaymentSchedule(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer loanID) {
final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=repaymentSchedule&" + Utils.TENANT_IDENTIFIER;
@@ -1002,6 +1009,12 @@ public class LoanTransactionHelper {
return new Gson().toJson(map);
}
public String getLoanFraudPayloadAsJSON(final String attrName, final String attrValue) {
final HashMap<String, String> map = new HashMap<>();
map.put(attrName, attrValue);
return new Gson().toJson(map);
}
public HashMap createTrancheDetail(final String id, final String date, final String amount) {
HashMap<String, Object> detail = new HashMap<>();
if (id != null) {