diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index 3c44b1e59..06062d49c 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -103,6 +103,7 @@ import org.apache.fineract.client.services.RecurringDepositAccountTransactionsAp import org.apache.fineract.client.services.RecurringDepositProductApi; import org.apache.fineract.client.services.ReportMailingJobsApi; import org.apache.fineract.client.services.ReportsApi; +import org.apache.fineract.client.services.RescheduleLoansApi; import org.apache.fineract.client.services.RolesApi; import org.apache.fineract.client.services.RunReportsApi; import org.apache.fineract.client.services.SavingsAccountApi; @@ -234,6 +235,7 @@ public final class FineractClient { public final RecurringDepositProductApi recurringDepositProducts; public final ReportMailingJobsApi reportMailingJobs; public final ReportsApi reports; + public final RescheduleLoansApi rescheduleLoans; public final RolesApi roles; public final RunReportsApi reportsRun; public final SavingsAccountApi savingsAccounts; @@ -346,6 +348,7 @@ public final class FineractClient { recurringDepositProducts = retrofit.create(RecurringDepositProductApi.class); reportMailingJobs = retrofit.create(ReportMailingJobsApi.class); reports = retrofit.create(ReportsApi.class); + rescheduleLoans = retrofit.create(RescheduleLoansApi.class); roles = retrofit.create(RolesApi.class); reportsRun = retrofit.create(RunReportsApi.class); savingsAccounts = retrofit.create(SavingsAccountApi.class); diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java index 634450304..5b8d37d38 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java @@ -102,6 +102,8 @@ public class CommandStrategyProvider { "approveLoanCommandStrategy"); this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\?command=disburse").method("POST").build(), "disburseLoanCommandStrategy"); + this.commandStrategies.put(CommandContext.resource("rescheduleloans").method("POST").build(), + "createLoanRescheduleRequestCommandStrategy"); this.commandStrategies.put(CommandContext.resource("rescheduleloans\\/\\d+\\?command=approve").method("POST").build(), "approveLoanRescheduleCommandStrategy"); this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\/\\d+").method("GET").build(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanRescheduleRequestCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanRescheduleRequestCommandStrategy.java new file mode 100644 index 000000000..256c5f050 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanRescheduleRequestCommandStrategy.java @@ -0,0 +1,65 @@ +/** + * 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.batch.command.internal; + +import javax.ws.rs.core.UriInfo; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.portfolio.loanaccount.rescheduleloan.api.RescheduleLoansApiResource; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and applies a new reschedule loan request on an existing loan. It passes the + * contents of the body from the BatchRequest to {@link RescheduleLoansApiResource} and gets back the response. This + * class will also catch any errors raised by {@link RescheduleLoansApiResource} and map those errors to appropriate + * status codes in BatchResponse. + * + * @see CommandStrategy + * @see BatchRequest + * @see BatchResponse + */ +@Component +@RequiredArgsConstructor +public class CreateLoanRescheduleRequestCommandStrategy implements CommandStrategy { + + private final RescheduleLoansApiResource rescheduleLoansApiResource; + + @Override + public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) { + + final BatchResponse response = new BatchResponse(); + final String responseBody; + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + // Calls 'createLoanRescheduleRequest' function from + // 'RescheduleLoansApiResource' to create a reschedule request on an existing loan + responseBody = rescheduleLoansApiResource.createLoanRescheduleRequest(request.getBody()); + + response.setStatusCode(200); + // Sets the body of the response after savings is successfully + // applied + response.setBody(responseBody); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java index 2fe5583d3..6f5beda69 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java @@ -320,7 +320,8 @@ final class ClientsApiResourceSwagger { public List datatables; @Schema(description = "Address requests") public List address; - + @Schema(example = "test@test.com") + public String emailAddress; } @Schema(description = "PostClientsResponse") 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 6b12d2d02..0a72a8dd6 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 @@ -291,44 +291,44 @@ final class LoansApiResourceSwagger { public Double principalDue; @Schema(example = "200.000000") public Double principalPaid; - @Schema(example = "0") - public Long principalWrittenOff; + @Schema(example = "0.000000") + public Double principalWrittenOff; @Schema(example = "20.000000") public Double principalOutstanding; @Schema(example = "20.000000") public Double principalLoanBalanceOutstanding; - @Schema(example = "0") - public Long interestOriginalDue; - @Schema(example = "0") - public Long interestDue; - @Schema(example = "0") - public Long interestPaid; - @Schema(example = "0") - public Long interestWaived; - @Schema(example = "0") - public Long interestWrittenOff; - @Schema(example = "0") - public Long interestOutstanding; - @Schema(example = "0") - public Long feeChargesDue; - @Schema(example = "20") - public Long feeChargesPaid; - @Schema(example = "20") - public Long feeChargesWaived; - @Schema(example = "20") - public Long feeChargesWrittenOff; - @Schema(example = "20") - public Long feeChargesOutstanding; - @Schema(example = "20") - public Long penaltyChargesDue; - @Schema(example = "20") - public Long penaltyChargesPaid; - @Schema(example = "20") - public Long penaltyChargesWaived; - @Schema(example = "20") - public Long penaltyChargesWrittenOff; - @Schema(example = "20") - public Long penaltyChargesOutstanding; + @Schema(example = "0.000000") + public Double interestOriginalDue; + @Schema(example = "0.000000") + public Double interestDue; + @Schema(example = "0.000000") + public Double interestPaid; + @Schema(example = "0.000000") + public Double interestWaived; + @Schema(example = "0.000000") + public Double interestWrittenOff; + @Schema(example = "0.000000") + public Double interestOutstanding; + @Schema(example = "0.000000") + public Double feeChargesDue; + @Schema(example = "20.000000") + public Double feeChargesPaid; + @Schema(example = "20.000000") + public Double feeChargesWaived; + @Schema(example = "20.000000") + public Double feeChargesWrittenOff; + @Schema(example = "20.000000") + public Double feeChargesOutstanding; + @Schema(example = "20.000000") + public Double penaltyChargesDue; + @Schema(example = "20.000000") + public Double penaltyChargesPaid; + @Schema(example = "20.000000") + public Double penaltyChargesWaived; + @Schema(example = "20.000000") + public Double penaltyChargesWrittenOff; + @Schema(example = "20.000000") + public Double penaltyChargesOutstanding; @Schema(example = "20.000000") public Double totalOriginalDueForPeriod; @Schema(example = "20.000000") @@ -337,20 +337,48 @@ final class LoansApiResourceSwagger { public Double totalPaidForPeriod; @Schema(example = "20.000000") public Double totalPaidInAdvanceForPeriod; - @Schema(example = "20") - public Long totalPaidLateForPeriod; - @Schema(example = "20") - public Long totalWaivedForPeriod; - @Schema(example = "20") - public Long totalWrittenOffForPeriod; + @Schema(example = "20.000000") + public Double totalPaidLateForPeriod; + @Schema(example = "20.000000") + public Double totalWaivedForPeriod; + @Schema(example = "20.000000") + public Double totalWrittenOffForPeriod; @Schema(example = "200.000000") public Double totalOutstandingForPeriod; - @Schema(example = "20") - public Long totalActualCostOfLoanForPeriod; + @Schema(example = "20.000000") + public Double totalActualCostOfLoanForPeriod; @Schema(example = "200.000000") public Double totalInstallmentAmountForPeriod; } + static final class GetLoansLoanIdDisbursementDetails { + + private GetLoansLoanIdDisbursementDetails() {} + + @Schema(example = "71") + public Integer id; + @Schema(example = "[2022, 07, 01]") + public LocalDate expectedDisbursementDate; + @Schema(example = "[2022, 07, 01]") + public LocalDate actualDisbursementDate; + @Schema(example = "22000.000000") + public Double principal; + @Schema(example = "22000.000000") + public Double netDisbursalAmount; + @Schema(example = "1") + public String loanChargeId; + @Schema(example = "22000.000000") + public Double chargeAmount; + @Schema(example = "22000.000000") + public Double waivedChargeAmount; + @Schema(example = "dd MMMM yyyy") + public String dateFormat; + @Schema(example = "de_DE") + public String locale; + @Schema(example = "some note") + public String note; + } + static final class GetLoansLoanIdSummary { private GetLoansLoanIdSummary() {} @@ -370,20 +398,6 @@ final class LoansApiResourceSwagger { public Long accountNo; } - static final class GetLoansLoanIdDisbursementDetails { - - private GetLoansLoanIdDisbursementDetails() {} - - @Schema(example = "71") - public Integer id; - @Schema(example = "[2013, 11, 1]") - public LocalDate expectedDisbursementDate; - @Schema(example = "22000.000000") - public Float principal; - @Schema(example = "22000.000000") - public Float approvedPrincipal; - } - static final class GetLoansLoanIdOverdueCharges { private GetLoansLoanIdOverdueCharges() {} @@ -457,78 +471,84 @@ final class LoansApiResourceSwagger { } public GetLoansLoanIdCurrency currency; - @Schema(example = "1000000") - public Long principalDisbursed; - @Schema(example = "0") - public Long principalPaid; - @Schema(example = "0") - public Long principalWrittenOff; - @Schema(example = "1000000") - public Long principalOutstanding; - @Schema(example = "833333.3") + @Schema(example = "1000000.000000") + public Double principalDisbursed; + @Schema(example = "0.000000") + public Double principalPaid; + @Schema(example = "0.000000") + public Double principalWrittenOff; + @Schema(example = "1000000.000000") + public Double principalOutstanding; + @Schema(example = "833333.300000") public Double principalOverdue; - @Schema(example = "240000") - public Long interestCharged; - @Schema(example = "0") - public Long interestPaid; - @Schema(example = "0") - public Long interestWaived; - @Schema(example = "0") - public Long interestWrittenOff; - @Schema(example = "240000") - public Long interestOutstanding; - @Schema(example = "200000") - public Long interestOverdue; - @Schema(example = "18000") - public Long feeChargesCharged; - @Schema(example = "0") - public Long feeChargesDueAtDisbursementCharged; - @Schema(example = "0") - public Long feeChargesPaid; - @Schema(example = "0") - public Long feeChargesWaived; - @Schema(example = "0") - public Long feeChargesWrittenOff; - @Schema(example = "18000") - public Long feeChargesOutstanding; - @Schema(example = "15000") - public Long feeChargesOverdue; - @Schema(example = "0") - public Long penaltyChargesCharged; - @Schema(example = "0") - public Long penaltyChargesPaid; - @Schema(example = "0") - public Long penaltyChargesWaived; - @Schema(example = "0") - public Long penaltyChargesWrittenOff; - @Schema(example = "0") - public Long penaltyChargesOutstanding; - @Schema(example = "0") - public Long penaltyChargesOverdue; - @Schema(example = "1258000") - public Long totalExpectedRepayment; - @Schema(example = "0") - public Long totalRepayment; - @Schema(example = "258000") - public Long totalExpectedCostOfLoan; - @Schema(example = "0") - public Long totalCostOfLoan; - @Schema(example = "0") - public Long totalWaived; - @Schema(example = "0") - public Long totalWrittenOff; - @Schema(example = "1258000") - public Long totalOutstanding; - @Schema(example = "1048333.3") + @Schema(example = "240000.000000") + public Double interestCharged; + @Schema(example = "0.000000") + public Double interestPaid; + @Schema(example = "0.000000") + public Double interestWaived; + @Schema(example = "0.000000") + public Double interestWrittenOff; + @Schema(example = "240000.000000") + public Double interestOutstanding; + @Schema(example = "200000.000000") + public Double interestOverdue; + @Schema(example = "18000.000000") + public Double feeChargesCharged; + @Schema(example = "0.000000") + public Double feeChargesDueAtDisbursementCharged; + @Schema(example = "0.000000") + public Double feeChargesPaid; + @Schema(example = "0.000000") + public Double feeChargesWaived; + @Schema(example = "0.000000") + public Double feeChargesWrittenOff; + @Schema(example = "18000.000000") + public Double feeChargesOutstanding; + @Schema(example = "15000.000000") + public Double feeChargesOverdue; + @Schema(example = "0.000000") + public Double penaltyChargesCharged; + @Schema(example = "0.000000") + public Double penaltyChargesPaid; + @Schema(example = "0.000000") + public Double penaltyChargesWaived; + @Schema(example = "0.000000") + public Double penaltyChargesWrittenOff; + @Schema(example = "0.000000") + public Double penaltyChargesOutstanding; + @Schema(example = "0.000000") + public Double penaltyChargesOverdue; + @Schema(example = "1258000.000000") + public Double totalExpectedRepayment; + @Schema(example = "0.000000") + public Double totalRepayment; + @Schema(example = "258000.000000") + public Double totalExpectedCostOfLoan; + @Schema(example = "0.000000") + public Double totalCostOfLoan; + @Schema(example = "0.000000") + public Double totalWaived; + @Schema(example = "0.000000") + public Double totalWrittenOff; + @Schema(example = "1258000.000000") + public Double totalOutstanding; + @Schema(example = "1048333.30000") public Double totalOverdue; + @Schema(example = "2456.30000") + public Double totalRecovered; @Schema(example = "[2012, 5, 10]") public LocalDate overdueSinceDate; + @Schema(example = "1") + public Long writeoffReasonId; + @Schema(example = "reason") + public String writeoffReason; public GetLoansLoanIdLinkedAccount linkedAccount; public Set disbursementDetails; @Schema(example = "1100.000000") - public Float fixedEmiAmount; - @Schema(example = "35000") - public Long maxOutstandingLoanBalance; + public Double fixedEmiAmount; + @Schema(example = "35000.000000") + public Double maxOutstandingLoanBalance; @Schema(example = "false") public Boolean canDisburse; public Set emiAmountVariations; @@ -539,6 +559,343 @@ final class LoansApiResourceSwagger { public Set overdueCharges; } + static final class GetLoansLoanIdPaymentType { + + private GetLoansLoanIdPaymentType() {} + + @Schema(example = "11") + public Integer id; + @Schema(example = "Cash") + public String name; + @Schema(example = "Cash Payment") + public String description; + @Schema(example = "true") + public Boolean isCashPayment; + @Schema(example = "0") + public Long position; + } + + static final class GetLoansLoanIdTransactions { + + private GetLoansLoanIdTransactions() {} + + static final class GetLoansLoanIdLoanTransactionEnumData { + + private GetLoansLoanIdLoanTransactionEnumData() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "loanTransactionType.repayment") + public String code; + @Schema(example = "2") + public String value; + + @Schema(example = "false") + public boolean disbursement; + @Schema(example = "false") + public boolean repaymentAtDisbursement; + @Schema(example = "true") + public boolean repayment; + @Schema(example = "false") + public boolean merchantIssuedRefund; + @Schema(example = "false") + public boolean payoutRefund; + @Schema(example = "false") + public boolean goodwillCredit; + @Schema(example = "false") + public boolean contra; + @Schema(example = "false") + public boolean waiveInterest; + @Schema(example = "false") + public boolean waiveCharges; + @Schema(example = "false") + public boolean accrual; + @Schema(example = "false") + public boolean writeOff; + @Schema(example = "false") + public boolean recoveryRepayment; + @Schema(example = "false") + public boolean initiateTransfer; + @Schema(example = "false") + public boolean approveTransfer; + @Schema(example = "false") + public boolean withdrawTransfer; + @Schema(example = "false") + public boolean rejectTransfer; + @Schema(example = "false") + public boolean chargePayment; + @Schema(example = "false") + public boolean refund; + @Schema(example = "false") + public boolean refundForActiveLoans; + @Schema(example = "false") + public boolean creditBalanceRefund; + } + + static final class GetLoansLoanIdPaymentDetailData { + + private GetLoansLoanIdPaymentDetailData() {} + + @Schema(example = "62") + public Integer id; + public GetLoansLoanIdPaymentType paymentType; + @Schema(example = "acc123") + public String accountNumber; + @Schema(example = "che123") + public String checkNumber; + @Schema(example = "rou123") + public String routingCode; + @Schema(example = "rec123") + public String receiptNumber; + @Schema(example = "ban123") + public String bankNumber; + } + + static final class GetLoansLoanIdLoanChargePaidByData { + + private GetLoansLoanIdLoanChargePaidByData() {} + + @Schema(example = "11") + public Long id; + @Schema(example = "100.000000") + public Double amount; + @Schema(example = "9679") + public Integer installmentNumber; + @Schema(example = "1") + public Long chargeId; + @Schema(example = "636") + public Long transactionId; + @Schema(example = "name") + public String name; + } + + static final class GetLoansLoanIdCodeValueData { + + private GetLoansLoanIdCodeValueData() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "code name") + public String name; + @Schema(example = "0") + public Integer position; + @Schema(example = "code description") + public String description; + @Schema(example = "true") + public Boolean active; + @Schema(example = "false") + public Boolean mandatory; + } + + static final class GetLoansLoanIdLoanRepaymentScheduleInstallmentData { + + private GetLoansLoanIdLoanRepaymentScheduleInstallmentData() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "11") + public Integer installmentId; + @Schema(example = "[2022, 07, 01]") + public LocalDate date; + @Schema(example = "100.000000") + public BigDecimal amount; + } + + @Schema(example = "1") + public Long id; + @Schema(example = "1") + public Long officeId; + @Schema(example = "Head Office") + public String officeName; + @Schema(description = "Transaction type") + public GetLoansLoanIdLoanTransactionEnumData type; + @Schema(example = "[2022, 07, 01]") + public LocalDate date; + @Schema(description = "Currency") + public GetLoansLoanIdCurrency currency; + @Schema(description = "Payment detail") + public GetLoansLoanIdPaymentDetailData paymentDetailData; + @Schema(example = "100.000000") + public Double amount; + @Schema(example = "100.000000") + public Double netDisbursalAmount; + @Schema(example = "100.000000") + public Double principalPortion; + @Schema(example = "100.000000") + public Double interestPortion; + @Schema(example = "100.000000") + public Double feeChargesPortion; + @Schema(example = "100.000000") + public Double penaltyChargesPortion; + @Schema(example = "100.000000") + public Double overpaymentPortion; + @Schema(example = "100.000000") + public Double unrecognizedIncomePortion; + @Schema(example = "3") + public String externalId; + @Schema(example = "100.000000") + public Double fixedEmiAmount; + @Schema(example = "100.000000") + public Double outstandingLoanBalance; + @Schema(example = "[2022, 07, 01]") + public LocalDate submittedOnDate; + public boolean manuallyReversed; + @Schema(example = "[2022, 07, 01]") + public LocalDate possibleNextRepaymentDate; + @Schema(description = "List of GetLoansLoanIdLoanChargePaidByData") + public List loanChargePaidByList; + @Schema(description = "List of GetLoansLoanIdPaymentType") + public List paymentTypeOptions; + @Schema(description = "List of GetLoansLoanIdCodeValueData") + public List writeOffReasonOptions; + @Schema(example = "4") + public Integer numberOfRepayments; + @Schema(example = "dd MMMM yyyy") + public String dateFormat; + @Schema(example = "de_DE") + public String locale; + @Schema(example = "100.000000") + public Double transactionAmount; + @Schema(example = "[2022, 07, 01]") + public LocalDate transactionDate; + @Schema(example = "101") + public Long paymentTypeId; + @Schema(example = "acct123") + public String accountNumber; + @Schema(example = "10001") + public Integer checkNumber; + @Schema(example = "6337") + public Integer routingCode; + @Schema(example = "67863") + public Integer receiptNumber; + @Schema(example = "34645568") + public Integer bankNumber; + @Schema(example = "7327") + public Long accountId; + @Schema(example = "repayment") + public String transactionType; + @Schema(description = "List of GetLoansLoanIdLoanRepaymentScheduleInstallmentData") + public List loanRepaymentScheduleInstallments; + } + + static final class GetLoansLoanIdLoanChargeData { + + private GetLoansLoanIdLoanChargeData() {} + + static final class GetLoansLoanIdEnumOptionData { + + private GetLoansLoanIdEnumOptionData() {} + + @Schema(example = "2") + public Long id; + @Schema(example = "chargeTimeType.specifiedDueDate") + public String code; + @Schema(example = "Specified due date") + public String value; + } + + static final class GetLoansLoanIdLoanInstallmentChargeData { + + private GetLoansLoanIdLoanInstallmentChargeData() {} + + @Schema(example = "2") + public Integer installmentNumber; + @Schema(example = "[2022, 07, 01]") + public LocalDate dueDate; + @Schema(example = "13.560000") + public Double amount; + @Schema(example = "13.560000") + public Double amountOutstanding; + @Schema(example = "13.560000") + public Double amountWaived; + @Schema(example = "false") + public boolean paid; + @Schema(example = "false") + public boolean waived; + @Schema(example = "13.560000") + public Double amountAccrued; + @Schema(example = "13.560000") + public Double amountUnrecognized; + } + + @Schema(example = "3") + public Long id; + @Schema(example = "5") + public Long chargeId; + @Schema(example = "snooze fee") + public String name; + @Schema(description = "Enum option data") + public GetLoansLoanIdEnumOptionData chargeTimeType; + @Schema(example = "[2022, 07, 01]") + public LocalDate dueDate; + @Schema(description = "Enum option data") + public GetLoansLoanIdEnumOptionData chargeCalculationType; + @Schema(example = "3.400000") + public Double percentage; + @Schema(example = "13.560000") + public Double amountPercentageAppliedTo; + @Schema(description = "currency") + public GetLoansLoanIdCurrency currency; + @Schema(example = "102.000000") + public Double amount; + @Schema(example = "12.000000") + public Double amountPaid; + @Schema(example = "14.000000") + public Double amountWaived; + @Schema(example = "102.000000") + public Double amountWrittenOff; + @Schema(example = "102.000000") + public Double amountOutstanding; + @Schema(example = "102.000000") + public Double amountOrPercentage; + @Schema(example = "false") + public boolean penalty; + @Schema(description = "Enum option data") + public GetLoansLoanIdEnumOptionData chargePaymentMode; + @Schema(example = "false") + public boolean paid; + @Schema(example = "false") + public boolean waived; + @Schema(example = "false") + public boolean chargePayable; + @Schema(example = "3") + public Long loanId; + @Schema(example = "30.000000") + public Double minCap; + @Schema(example = "30.000000") + public Double maxCap; + @Schema(description = "List of GetLoansLoanIdLoanInstallmentChargeData") + public List installmentChargeData; + @Schema(example = "30.000000") + private Double amountAccrued; + @Schema(example = "30.000000") + private Double amountUnrecognized; + @Schema(example = "3ert3453") + private String externalId; + } + + static final class GetLoansLoanIdCollectionData { + + private GetLoansLoanIdCollectionData() {} + + @Schema(example = "100.000000") + public Double availableDisbursementAmount; + @Schema(example = "12") + public Integer pastDueDays; + @Schema(example = "[2022, 07, 01]") + public LocalDate nextPaymentDueDate; + @Schema(example = "4") + public Integer delinquentDays; + @Schema(example = "[2022, 07, 01]") + public LocalDate delinquentDate; + @Schema(example = "100.000000") + public Double delinquentAmount; + @Schema(example = "[2022, 07, 01]") + public LocalDate lastPaymentDate; + @Schema(example = "100.000000") + public Double lastPaymentAmount; + } + @Schema(example = "1") public Long id; @Schema(example = "000000001") @@ -566,7 +923,7 @@ final class LoansApiResourceSwagger { public String loanOfficerName; public GetLoansLoanIdLoanType loanType; public GetLoansLoanIdCurrency currency; - @Schema(example = "1000000") + @Schema(example = "1000000.00") public BigDecimal principal; @Schema(example = "1000.000000") public Double approvedPrincipal; @@ -595,6 +952,14 @@ final class LoansApiResourceSwagger { public GetLoansLoanIdTimeline timeline; public GetLoansLoanIdSummary summary; public GetLoansLoanIdRepaymentSchedule repaymentSchedule; + @Schema(description = "Set of GetLoansLoanIdDisbursementDetails") + public Set transactions; + @Schema(description = "Set of GetLoansLoanIdDisbursementDetails") + public Set disbursementDetails; + @Schema(description = "Delinquent data") + public GetLoansLoanIdCollectionData delinquent; + @Schema(description = "Set of charges") + public Set charges; } @Schema(description = "GetLoansResponse") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java index 2496f8f78..490321146 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java @@ -18,6 +18,13 @@ */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.api; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.HashSet; import java.util.List; @@ -86,6 +93,9 @@ public class RescheduleLoansApiResource { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve all reschedule loan reasons", description = "Retrieve all reschedule loan reasons as a template") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RescheduleLoansApiResourceSwagger.GetRescheduleReasonsTemplateResponse.class))) }) public String retrieveTemplate(@Context final UriInfo uriInfo) { this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME); @@ -102,6 +112,9 @@ public class RescheduleLoansApiResource { @Path("{scheduleId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve loan reschedule request by schedule id", description = "Retrieve loan reschedule request by schedule id") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RescheduleLoansApiResourceSwagger.GetLoanRescheduleRequestResponse.class))) }) public String readLoanRescheduleRequest(@Context final UriInfo uriInfo, @PathParam("scheduleId") final Long scheduleId, @QueryParam("command") final String command) { this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME); @@ -123,6 +136,10 @@ public class RescheduleLoansApiResource { @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Create loan reschedule request", description = "Create a loan reschedule request.") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = String.class)), description = "{\"submittedOnDate\": \"05/02/2022\",\"rescheduleFromDate\": \"05/02/2022\",\"rescheduleReasonId\": 67,\"adjustedDueDate\": \"07/01/2022\",\"loanId\": \"18\",\"dateFormat\": \"MM/dd/yyyy\",\"locale\": \"en\"}") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RescheduleLoansApiResourceSwagger.PostCreateRescheduleLoansResponse.class))) }) public String createLoanRescheduleRequest(final String apiRequestBodyAsJson) { final CommandWrapper commandWrapper = new CommandWrapperBuilder() .createLoanRescheduleRequest(RescheduleLoansApiConstants.ENTITY_NAME).withJson(apiRequestBodyAsJson).build(); @@ -136,6 +153,10 @@ public class RescheduleLoansApiResource { @Path("{scheduleId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Update loan reschedule request", description = "Update a loan reschedule request by either approving/rejecting it.") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = String.class)), description = "{\"approvedOnDate\": \"05/02/2022\",\"dateFormat\": \"MM/dd/yyyy\",\"locale\": \"en\"}") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RescheduleLoansApiResourceSwagger.PostUpdateRescheduleLoansResponse.class))) }) public String updateLoanRescheduleRequest(@PathParam("scheduleId") final Long scheduleId, @QueryParam("command") final String command, final String apiRequestBodyAsJson) { CommandWrapper commandWrapper = null; @@ -175,6 +196,9 @@ public class RescheduleLoansApiResource { @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve all reschedule requests", description = "Retrieve all reschedule requests.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RescheduleLoansApiResourceSwagger.GetLoanRescheduleRequestResponse.class)))) }) public String retrieveAllRescheduleRequest(@Context final UriInfo uriInfo, @QueryParam("command") final String command) { this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResourceSwagger.java new file mode 100644 index 000000000..49dc52762 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResourceSwagger.java @@ -0,0 +1,221 @@ +/** + * 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.rescheduleloan.api; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Set; + +/** + * Reschedule Loans Api Resource Swagger. + */ +final class RescheduleLoansApiResourceSwagger { + + private RescheduleLoansApiResourceSwagger() {} + + @Schema(description = "GetRescheduleReasonsTemplateResponse") + public static final class GetRescheduleReasonsTemplateResponse { + + private GetRescheduleReasonsTemplateResponse() {} + + static final class GetRescheduleReasonsAllowedTypes { + + private GetRescheduleReasonsAllowedTypes() {} + + @Schema(example = "1") + public Integer id; + @Schema(example = "reason") + public String name; + @Schema(example = "0") + public Integer position; + @Schema(example = "description") + public String description; + @Schema(example = "true") + public Boolean active; + @Schema(example = "false") + public Boolean mandatory; + } + + public Set rescheduleReasons; + } + + @Schema(description = "GetLoanRescheduleRequestResponse") + public static final class GetLoanRescheduleRequestResponse { + + private GetLoanRescheduleRequestResponse() {} + + static final class GetLoanRescheduleRequestStatus { + + private GetLoanRescheduleRequestStatus() {} + + @Schema(example = "100") + public Integer id; + @Schema(example = "loanStatusType.submitted.and.pending.approval") + public String code; + @Schema(example = "Submitted and pending approval") + public String value; + @Schema(example = "true") + public Boolean pendingApproval; + @Schema(example = "false") + public Boolean approved; + @Schema(example = "false") + public Boolean rejected; + } + + static final class RescheduleReasonsCodeValue { + + private RescheduleReasonsCodeValue() {} + + @Schema(example = "1") + public Integer id; + @Schema(example = "reason") + public String name; + @Schema(example = "true") + public Boolean active; + @Schema(example = "false") + public Boolean mandatory; + } + + static final class RescheduleReasonsTimeline { + + private RescheduleReasonsTimeline() {} + + @Schema(example = "[2014, 5, 1]") + public LocalDate submittedOnDate; + @Schema(example = "mifos") + public String submittedByUsername; + @Schema(example = "App") + public String submittedByFirstname; + @Schema(example = "Administrator") + public String submittedByLastname; + @Schema(example = "[2014, 5, 1]") + public LocalDate approvedOnDate; + @Schema(example = "mifos") + public String approvedByUsername; + @Schema(example = "App") + public String approvedByFirstname; + @Schema(example = "Administrator") + public String approvedByLastname; + } + + static final class LoanTermTypeOptions { + + private LoanTermTypeOptions() {} + + @Schema(example = "2") + public Integer id; + @Schema(example = "loanTermType.dueDate") + public String code; + @Schema(example = "dueDate") + public String value; + } + + static final class LoanTermVariationsData { + + private LoanTermVariationsData() {} + + @Schema(example = "1") + public Integer id; + public LoanTermTypeOptions termType; + @Schema(example = "[2022, 5, 1]") + public LocalDate termVariationApplicableFrom; + @Schema(example = "100.00") + public BigDecimal decimalValue; + @Schema(example = "[2022, 5, 2]") + public LocalDate dateValue; + @Schema(example = "true") + public Boolean isSpecificToInstallment; + @Schema(example = "true") + public Boolean isProcessed; + + } + + @Schema(example = "1") + public Integer id; + @Schema(example = "18") + public Integer loanId; + @Schema(example = "15") + public Integer clientId; + @Schema(example = "clientName") + public String clientName; + @Schema(example = "000000018") + public String loanAccountNumber; + + public GetLoanRescheduleRequestStatus statusEnum; + + @Schema(example = "1") + public Integer rescheduleFromInstallment; + @Schema(example = "[2022, 5, 1]") + public LocalDate rescheduleFromDate; + @Schema(example = "false") + public Boolean recalculateInterest; + public RescheduleReasonsCodeValue rescheduleReasonCodeValue; + public RescheduleReasonsTimeline timeline; + @Schema(example = "rescheduleReasonComment") + public String rescheduleReasonComment; + public Set loanTermVariationsData; + } + + @Schema(description = "PostCreateRescheduleLoansResponse ") + public static final class PostCreateRescheduleLoansResponse { + + private PostCreateRescheduleLoansResponse() {} + + @Schema(example = "1") + public Long officeId; + @Schema(example = "12") + public Long clientId; + @Schema(example = "18") + public Long loanId; + @Schema(example = "12") + public Long resourceId; + } + + @Schema(description = "PostUpdateRescheduleLoansResponse ") + public static final class PostUpdateRescheduleLoansResponse { + + private PostUpdateRescheduleLoansResponse() {} + + static final class PostUpdateRescheduleLoanChanges { + + private PostUpdateRescheduleLoanChanges() {} + + @Schema(example = "en") + public String locale; + @Schema(example = "en") + public String dateFormat; + @Schema(example = "05/02/2022") + public String approvedOnDate; + @Schema(example = "1") + public Long approvedByUserId; + } + + @Schema(example = "1") + public Long officeId; + @Schema(example = "12") + public Long clientId; + @Schema(example = "18") + public Long loanId; + @Schema(example = "12") + public Long resourceId; + public PostUpdateRescheduleLoanChanges changes; + } + +} 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 7c7dffe66..1c92ad6f5 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 @@ -2332,7 +2332,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { public CollectionData retrieveLoanCollectionData(Long loanId) { final CollectionDataMapper mapper = new CollectionDataMapper(sqlGenerator); String sql = "select " + mapper.schema(); - CollectionData collectionData = this.jdbcTemplate.queryForObject(sql, mapper, loanId); // NOSONAR + CollectionData collectionData = this.jdbcTemplate.queryForObject(sql, mapper, loanId, loanId); // NOSONAR return collectionData; } @@ -2363,7 +2363,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { sqlBuilder.append( "left join (select lt.loan_id, lt.transaction_date as transactionDate, lt.amount as amount from m_loan_transaction lt "); sqlBuilder.append( - "where lt.is_reversed = false and lt.transaction_type_enum=2 order by lt.transaction_date desc limit 1) lre on lre.loan_id = l.id "); + "where lt.loan_id =? and lt.is_reversed = false and lt.transaction_type_enum=2 order by lt.transaction_date desc limit 1) lre on lre.loan_id = l.id "); sqlBuilder.append("where l.id=? "); return sqlBuilder.toString(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResourceSwagger.java index 2f3091c58..36c943c19 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResourceSwagger.java @@ -228,10 +228,26 @@ final class SelfLoansApiResourceSwagger { public Integer id; @Schema(example = "[2013, 11, 1]") public LocalDate expectedDisbursementDate; + @Schema(example = "[2022, 07, 01]") + public LocalDate actualDisbursementDate; @Schema(example = "22000.000000") - public Float principal; + public Double principal; + @Schema(example = "22000.000000") + public Double netDisbursalAmount; + @Schema(example = "1") + public String loanChargeId; + @Schema(example = "22000.000000") + public Double chargeAmount; + @Schema(example = "22000.000000") + public Double waivedChargeAmount; @Schema(example = "22000.000000") public Float approvedPrincipal; + @Schema(example = "dd MMMM yyyy") + public String dateFormat; + @Schema(example = "de_DE") + public String locale; + @Schema(example = "some note") + public String note; } static final class GetLoansLoanIdOverdueCharges { @@ -337,78 +353,78 @@ final class SelfLoansApiResourceSwagger { } public GetLoansLoanIdCurrency currency; - @Schema(example = "1000000") - public Long principalDisbursed; - @Schema(example = "0") - public Long principalPaid; - @Schema(example = "0") - public Long principalWrittenOff; - @Schema(example = "1000000") - public Long principalOutstanding; - @Schema(example = "833333.3") + @Schema(example = "1000000.000000") + public Double principalDisbursed; + @Schema(example = "0.000000") + public Double principalPaid; + @Schema(example = "0.000000") + public Double principalWrittenOff; + @Schema(example = "1000000.000000") + public Double principalOutstanding; + @Schema(example = "833333.300000") public Double principalOverdue; - @Schema(example = "240000") - public Long interestCharged; - @Schema(example = "0") - public Long interestPaid; - @Schema(example = "0") - public Long interestWaived; - @Schema(example = "0") - public Long interestWrittenOff; - @Schema(example = "240000") - public Long interestOutstanding; - @Schema(example = "200000") - public Long interestOverdue; - @Schema(example = "18000") - public Long feeChargesCharged; - @Schema(example = "0") - public Long feeChargesDueAtDisbursementCharged; - @Schema(example = "0") - public Long feeChargesPaid; - @Schema(example = "0") - public Long feeChargesWaived; - @Schema(example = "0") - public Long feeChargesWrittenOff; - @Schema(example = "18000") - public Long feeChargesOutstanding; - @Schema(example = "15000") - public Long feeChargesOverdue; - @Schema(example = "0") - public Long penaltyChargesCharged; - @Schema(example = "0") - public Long penaltyChargesPaid; - @Schema(example = "0") - public Long penaltyChargesWaived; - @Schema(example = "0") - public Long penaltyChargesWrittenOff; - @Schema(example = "0") - public Long penaltyChargesOutstanding; - @Schema(example = "0") - public Long penaltyChargesOverdue; - @Schema(example = "1258000") - public Long totalExpectedRepayment; - @Schema(example = "0") - public Long totalRepayment; - @Schema(example = "258000") - public Long totalExpectedCostOfLoan; - @Schema(example = "0") - public Long totalCostOfLoan; - @Schema(example = "0") - public Long totalWaived; - @Schema(example = "0") - public Long totalWrittenOff; - @Schema(example = "1258000") - public Long totalOutstanding; - @Schema(example = "1048333.3") + @Schema(example = "240000.000000") + public Double interestCharged; + @Schema(example = "0.000000") + public Double interestPaid; + @Schema(example = "0.000000") + public Double interestWaived; + @Schema(example = "0.000000") + public Double interestWrittenOff; + @Schema(example = "240000.000000") + public Double interestOutstanding; + @Schema(example = "200000.000000") + public Double interestOverdue; + @Schema(example = "18000.000000") + public Double feeChargesCharged; + @Schema(example = "0.000000") + public Double feeChargesDueAtDisbursementCharged; + @Schema(example = "0.000000") + public Double feeChargesPaid; + @Schema(example = "0.000000") + public Double feeChargesWaived; + @Schema(example = "0.000000") + public Double feeChargesWrittenOff; + @Schema(example = "18000.000000") + public Double feeChargesOutstanding; + @Schema(example = "15000.000000") + public Double feeChargesOverdue; + @Schema(example = "0.000000") + public Double penaltyChargesCharged; + @Schema(example = "0.000000") + public Double penaltyChargesPaid; + @Schema(example = "0.000000") + public Double penaltyChargesWaived; + @Schema(example = "0.000000") + public Double penaltyChargesWrittenOff; + @Schema(example = "0.000000") + public Double penaltyChargesOutstanding; + @Schema(example = "0.000000") + public Double penaltyChargesOverdue; + @Schema(example = "1258000.000000") + public Double totalExpectedRepayment; + @Schema(example = "0.000000") + public Double totalRepayment; + @Schema(example = "258000.000000") + public Double totalExpectedCostOfLoan; + @Schema(example = "0.000000") + public Double totalCostOfLoan; + @Schema(example = "0.000000") + public Double totalWaived; + @Schema(example = "0.000000") + public Double totalWrittenOff; + @Schema(example = "1258000.000000") + public Double totalOutstanding; + @Schema(example = "1048333.30000") public Double totalOverdue; @Schema(example = "[2012, 5, 10]") public LocalDate overdueSinceDate; public GetLoansLoanIdLinkedAccount linkedAccount; public Set disbursementDetails; @Schema(example = "1100.000000") - public Float fixedEmiAmount; - @Schema(example = "35000") - public Long maxOutstandingLoanBalance; + public Double fixedEmiAmount; + @Schema(example = "35000.000000") + public Double maxOutstandingLoanBalance; @Schema(example = "false") public Boolean canDisburse; public Set emiAmountVariations; diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java index 22e8811d8..acf818ce3 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java @@ -32,6 +32,7 @@ import org.apache.fineract.batch.command.internal.ApproveLoanRescheduleCommandSt import org.apache.fineract.batch.command.internal.CollectChargesCommandStrategy; import org.apache.fineract.batch.command.internal.CreateChargeCommandStrategy; import org.apache.fineract.batch.command.internal.CreateClientCommandStrategy; +import org.apache.fineract.batch.command.internal.CreateLoanRescheduleRequestCommandStrategy; import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy; import org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy; import org.apache.fineract.batch.command.internal.GetDatatableEntryByAppTableIdCommandStrategy; @@ -77,6 +78,8 @@ public class CommandStrategyProviderTest { mock(ApproveLoanCommandStrategy.class)), Arguments.of("loans/123?command=disburse", HttpMethod.POST, "disburseLoanCommandStrategy", mock(DisburseLoanCommandStrategy.class)), + Arguments.of("rescheduleloans", HttpMethod.POST, "createLoanRescheduleRequestCommandStrategy", + mock(CreateLoanRescheduleRequestCommandStrategy.class)), Arguments.of("rescheduleloans/123?command=approve", HttpMethod.POST, "approveLoanRescheduleCommandStrategy", mock(ApproveLoanRescheduleCommandStrategy.class)), Arguments.of("loans/123/transactions/123", HttpMethod.GET, "getTransactionByIdCommandStrategy", diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateLoanRescheduleRequestCommandStrategyTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateLoanRescheduleRequestCommandStrategyTest.java new file mode 100644 index 000000000..0cdf40599 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateLoanRescheduleRequestCommandStrategyTest.java @@ -0,0 +1,113 @@ +/** + * 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.batch.command.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.UriInfo; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.portfolio.loanaccount.rescheduleloan.api.RescheduleLoansApiResource; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class fpr {@link CreateLoanRescheduleRequestCommandStrategy}. + */ +public class CreateLoanRescheduleRequestCommandStrategyTest { + + /** + * Test {@link CreateLoanRescheduleRequestCommandStrategy#execute} happy path scenario. + */ + @Test + public void testExecuteSuccessScenario() { + final TestContext testContext = new TestContext(); + final BatchRequest batchRequest = getBatchRequest(); + final String responseBody = "myResponseBody"; + + when(testContext.rescheduleLoansApiResource.createLoanRescheduleRequest(batchRequest.getBody())).thenReturn(responseBody); + + BatchResponse batchResponse = testContext.subjectToTest.execute(batchRequest, testContext.uriInfo); + + assertEquals(HttpStatus.SC_OK, batchResponse.getStatusCode()); + assertSame(responseBody, batchResponse.getBody()); + assertEquals(batchRequest.getRequestId(), batchResponse.getRequestId()); + assertEquals(batchRequest.getHeaders(), batchResponse.getHeaders()); + + verify(testContext.rescheduleLoansApiResource).createLoanRescheduleRequest(batchRequest.getBody()); + } + + /** + * Creates and returns a request with the given loan id and command value. + * + * + * @return BatchRequest + */ + private BatchRequest getBatchRequest() { + + final BatchRequest br = new BatchRequest(); + String relativeUrl = "rescheduleloans"; + + br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5))); + br.setRelativeUrl(relativeUrl); + br.setMethod(HttpMethod.POST); + br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5))); + br.setBody("{}"); + + return br; + } + + /** + * Private test context class used since testng runs in parallel to avoid state between tests + */ + private static class TestContext { + + /** + * Mock URI info. + */ + @Mock + private UriInfo uriInfo; + + /** + * Mock reschedule loans API resource. + */ + @Mock + private RescheduleLoansApiResource rescheduleLoansApiResource; + + /** + * The {@link CreateLoanRescheduleRequestCommandStrategy} under test. + */ + private final CreateLoanRescheduleRequestCommandStrategy subjectToTest; + + /** + * Constructor. + */ + TestContext() { + MockitoAnnotations.openMocks(this); + subjectToTest = new CreateLoanRescheduleRequestCommandStrategy(rescheduleLoansApiResource); + } + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java index 49bded81a..0123dd4be 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java @@ -27,8 +27,11 @@ 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.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy; import org.apache.fineract.batch.domain.BatchRequest; @@ -41,12 +44,15 @@ import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper; +import org.apache.fineract.integrationtests.common.system.CodeHelper; import org.apache.fineract.integrationtests.common.system.DatatableHelper; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.http.HttpStatus; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Test class for {@link org.apache.fineract.batch.command.CommandStrategyProvider}. This tests the response provided by @@ -59,6 +65,8 @@ import org.junit.jupiter.api.Test; */ public class BatchApiTest { + private static final Logger LOG = LoggerFactory.getLogger(BatchApiTest.class); + /** * The response specification */ @@ -718,6 +726,81 @@ public class BatchApiTest { Assertions.assertEquals(HttpStatus.SC_OK, responses.get(3).getStatusCode(), "Verify Status Code 200 for Get Transaction By Id"); } + /** + * Test for the successful new loan reschedule request and approval for the same. A '200' status code is expected on + * successful responses. + * + * @see org.apache.fineract.batch.command.internal.CreateLoanRescheduleRequestCommandStrategy + * @see org.apache.fineract.batch.command.internal.ApproveLoanRescheduleCommandStrategy + */ + @Test + public void shouldReturnOkStatusOnSuccessfulDisbursementAndRescheduleLoan() { + final String loanProductJSON = new LoanProductTestBuilder() // + .withPrincipal("10000000.00") // + .withNumberOfRepayments("24") // + .withRepaymentAfterEvery("1") // + .withRepaymentTypeAsMonth() // + .withinterestRatePerPeriod("2") // + .withInterestRateFrequencyTypeAsMonths() // + .withAmortizationTypeAsEqualPrincipalPayment() // + .withInterestTypeAsDecliningBalance() // + .currencyDetails("0", "100").build(null); + + final Long createActiveClientRequestId = 8462L; + final Long applyLoanRequestId = createActiveClientRequestId + 1; + final Long approveLoanRequestId = applyLoanRequestId + 1; + final Long disburseLoanRequestId = approveLoanRequestId + 1; + final Long rescheduleLoanRequestId = disburseLoanRequestId + 1; + final Long approveRescheduleLoanRequestId = rescheduleLoanRequestId + 1; + + // Create product + LOG.info("LoanProduct {}", loanProductJSON); + final Integer productId = new LoanTransactionHelper(this.requestSpec, this.responseSpec).getLoanProductId(loanProductJSON); + + // Create client + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientId); + + /* Retrieve/Create Code Values for the Code "LoanRescheduleReason = 23" */ + final HashMap codeValue = CodeHelper.retrieveOrCreateCodeValue(23, this.requestSpec, this.responseSpec); + + final Integer codeValueId = (Integer) codeValue.get("id"); + + // Create an ApplyLoan request + final BatchRequest applyLoanRequestWithClientId = BatchHelper.applyLoanRequestWithClientId(applyLoanRequestId, clientId, productId); + + // Create an approveLoan request + final BatchRequest approveLoanRequest = BatchHelper.approveLoanRequest(approveLoanRequestId, applyLoanRequestId); + + // Create a disbursement request + final LocalDate disburseLoanDate = LocalDate.now(ZoneId.systemDefault()).minusDays(1); + final BatchRequest disburseLoanRequest = BatchHelper.disburseLoanRequest(disburseLoanRequestId, approveLoanRequestId, + disburseLoanDate); + + // Create a reschedule loan request + final BatchRequest rescheduleLoanRequest = BatchHelper.createRescheduleLoanRequest(rescheduleLoanRequestId, disburseLoanRequestId, + disburseLoanDate.plusMonths(1), codeValueId); + + // Approve reschedule loan request + final BatchRequest approveRescheduleLoanRequest = BatchHelper.approveRescheduleLoanRequest(approveRescheduleLoanRequestId, + rescheduleLoanRequestId); + + final List batchRequests = Arrays.asList(applyLoanRequestWithClientId, approveLoanRequest, disburseLoanRequest, + rescheduleLoanRequest, approveRescheduleLoanRequest); + + LOG.info("shouldReturnOkStatusOnSuccessfulDisbursementAndRescheduleLoan Request - {}", BatchHelper.toJsonString(batchRequests)); + final List responses = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec, + BatchHelper.toJsonString(batchRequests)); + + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(0).getStatusCode(), "Verify Status Code 200 for Apply Loan"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(1).getStatusCode(), "Verify Status Code 200 for Approve Loan"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(2).getStatusCode(), "Verify Status Code 200 for Disburse Loan"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(3).getStatusCode(), + "Verify Status Code 200 for Create Reschedule Loan request"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(4).getStatusCode(), + "Verify Status Code 200 for Approve Reschedule Loan request"); + } + /** * Test for the successful create client, creat, approve and get loan. A '200' status code is expected on successful * responses. diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java index ff45e9974..3284f2785 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java @@ -98,6 +98,7 @@ public final class BatchHelper { public static List postBatchRequestsWithoutEnclosingTransaction(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String jsonifiedBatchRequests) { final String response = Utils.performServerPost(requestSpec, responseSpec, BATCH_API_URL, jsonifiedBatchRequests, null); + LOG.info("BatchHelper Response {}", response); return BatchHelper.fromJsonString(response); } @@ -501,8 +502,6 @@ public final class BatchHelper { * the reference ID * @param amount * the amount - * @param date - * the transaction date * @return BatchRequest the batch request */ public static BatchRequest repayLoanRequest(final Long requestId, final Long reference, final String amount) { @@ -578,6 +577,69 @@ public final class BatchHelper { return br; } + /** + * Creates and returns a + * {@link org.apache.fineract.batch.command.internal.CreateLoanRescheduleRequestCommandStrategy} request with given + * request ID. + * + * + * @param requestId + * the request ID + * @param reference + * teh reference + * @param rescheduleFromDate + * the reschedule from date + * @param rescheduleReasonId + * the reschedule reason code value id + * + * @return BatchRequest the created {@link BatchRequest} + */ + public static BatchRequest createRescheduleLoanRequest(final Long requestId, final Long reference, final LocalDate rescheduleFromDate, + final Integer rescheduleReasonId) { + final BatchRequest br = new BatchRequest(); + + br.setRequestId(requestId); + br.setReference(reference); + br.setRelativeUrl("rescheduleloans"); + br.setMethod("POST"); + final LocalDate today = LocalDate.now(ZoneId.systemDefault()); + final LocalDate adjustedDueDate = LocalDate.now(ZoneId.systemDefault()).plusDays(40); + final String submittedOnDate = today.format(DateTimeFormatter.ofPattern("dd MMMM yyyy")); + final String rescheduleFromDateString = rescheduleFromDate.format(DateTimeFormatter.ofPattern("dd MMMM yyyy")); + final String adjustedDueDateString = adjustedDueDate.format(DateTimeFormatter.ofPattern("dd MMMM yyyy")); + br.setBody(String.format("{\"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", " + + "\"submittedOnDate\": \"%s\", \"rescheduleFromDate\": \"%s\", \"rescheduleReasonId\": %d, \"adjustedDueDate\": \"%s\", \"loanId\": \"$.loanId\"}", + submittedOnDate, rescheduleFromDateString, rescheduleReasonId, adjustedDueDateString)); + + return br; + } + + /** + * Creates and returns a {@link org.apache.fineract.batch.command.internal.ApproveLoanRescheduleCommandStrategy} + * request with given request ID. + * + * + * @param requestId + * the request ID + * @param reference + * teh reference + * @return BatchRequest the created {@link BatchRequest} + */ + public static BatchRequest approveRescheduleLoanRequest(final Long requestId, final Long reference) { + final BatchRequest br = new BatchRequest(); + + br.setRequestId(requestId); + br.setReference(reference); + br.setRelativeUrl("rescheduleloans/$.resourceId?command=approve"); + br.setMethod("POST"); + final LocalDate approvedOnDate = LocalDate.now(ZoneId.systemDefault()); + final String approvedOnDateString = approvedOnDate.format(DateTimeFormatter.ofPattern("dd MMMM yyyy")); + br.setBody(String.format("{\"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", " + "\"approvedOnDate\": \"%s\"}", + approvedOnDateString)); + + return br; + } + /** * Checks that the client with given externalId is not created on the server. * diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java index 63d2bf5d3..3318787e5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java @@ -207,8 +207,9 @@ public class ClientHelper { map.put("locale", "en"); map.put("active", "true"); map.put("activationDate", dateOfJoining); - log.info("map : {}", map); - return GSON.toJson(map); + final String testClientAsJson = GSON.toJson(map); + log.info("TestClient Request : {}", testClientAsJson); + return testClientAsJson; } public static String getTestClientAsJSONPending(final String submittedOnDate, final String officeId) {