This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new b2482c922 Setting Fraud flag for a loan account
b2482c922 is described below
commit b2482c9226777b9ae8ac011bfab0a65f4fd663da
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Wed Oct 19 22:59:07 2022 -0500
Setting Fraud flag for a loan account
---
.../commands/service/CommandWrapperBuilder.java | 9 ++
.../loanaccount/api/LoanApiConstants.java | 5 +
.../loanaccount/api/LoansApiResource.java | 55 ++++----
.../loanaccount/api/LoansApiResourceSwagger.java | 4 +
.../loanaccount/data/LoanAccountData.java | 47 +++---
.../portfolio/loanaccount/domain/Loan.java | 11 ++
.../handler/MarkLoanAsFraudCommandHandler.java | 43 ++++++
.../LoanApplicationCommandFromApiJsonHelper.java | 2 +-
.../serialization/LoanEventApiJsonValidator.java | 21 +++
.../service/LoanReadPlatformServiceImpl.java | 8 +-
.../service/LoanWritePlatformService.java | 2 +
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 31 ++++
.../self/loanaccount/api/SelfLoansApiResource.java | 4 +-
.../db/changelog/tenant/changelog-tenant.xml | 1 +
.../parts/0062_add_fraud_attribute_to_loan.xml | 39 +++++
.../integrationtests/LoanAccountFraudTest.java | 157 +++++++++++++++++++++
.../common/loans/LoanTransactionHelper.java | 13 ++
17 files changed, 403 insertions(+), 49 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index a7327e7ca..d867511f1 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -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";
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index 42492928c..087c80acf 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -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";
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
index b37458424..ca4fc1639 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -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();
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index 34a066364..00be3eb35 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -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")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index c515e9d81..43c36eefb 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -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;
}
+
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 48b69595f..38dec1461 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -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;
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/MarkLoanAsFraudCommandHandler.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/MarkLoanAsFraudCommandHandler.java
new file mode 100644
index 000000000..e481cb5c9
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/MarkLoanAsFraudCommandHandler.java
@@ -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);
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
index 553c64f82..8e2a9dac2 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
@@ -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;
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
index 54348344a..01a76dc94 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
@@ -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)) {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index a80bbde22..26514e05e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -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);
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
index fb419edd7..d1db8f194 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
@@ -119,4 +119,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand
command);
+ CommandProcessingResult markLoanAsFraud(Long loanId, JsonCommand command);
+
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 85ef0522f..dd709051e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -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) {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResource.java
index 2e9c104a4..993d0ad64 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResource.java
@@ -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
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index d66da7972..8e90dbb53 100644
---
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -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>
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0062_add_fraud_attribute_to_loan.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0062_add_fraud_attribute_to_loan.xml
new file mode 100644
index 000000000..c5b283e27
--- /dev/null
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0062_add_fraud_attribute_to_loan.xml
@@ -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>
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountFraudTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountFraudTest.java
new file mode 100644
index 000000000..73d5ff71d
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountFraudTest.java
@@ -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);
+ }
+
+}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 7f30e4341..e80419c36 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -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) {