Feat: Repayment With Post Dated Checks

This commit is contained in:
BLasan
2021-09-21 21:34:58 +08:00
committed by Petri Tuomola
parent f0d4f5e332
commit 07f395ef7f
35 changed files with 1801 additions and 7 deletions
@@ -106,9 +106,9 @@ import org.apache.fineract.client.services.ReportsApi;
import org.apache.fineract.client.services.RolesApi;
import org.apache.fineract.client.services.RunReportsApi;
import org.apache.fineract.client.services.SavingsAccountApi;
import org.apache.fineract.client.services.SavingsAccountTransactionsApi;
import org.apache.fineract.client.services.SavingsChargesApi;
import org.apache.fineract.client.services.SavingsProductApi;
import org.apache.fineract.client.services.SavingsAccountTransactionsApi;
import org.apache.fineract.client.services.SchedulerApi;
import org.apache.fineract.client.services.ScoreCardApi;
import org.apache.fineract.client.services.SearchApiApi;
@@ -896,6 +896,33 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder updatePostDatedCheck(final Long id, final Long loanId) {
this.actionName = "UPDATE";
this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS";
this.entityId = id;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id;
return this;
}
public CommandWrapperBuilder bouncedCheck(final Long id, final Long loanId) {
this.actionName = "BOUNCE";
this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS";
this.entityId = id;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id;
return this;
}
public CommandWrapperBuilder deletePostDatedCheck(final Long id, final Long loanId) {
this.actionName = "DELETE";
this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS";
this.entityId = id;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id;
return this;
}
public CommandWrapperBuilder updateLoanApplication(final Long loanId) {
this.actionName = "UPDATE";
this.entityName = "LOAN";
@@ -433,6 +433,7 @@ public class LoanImportHandler implements ImportHandler {
gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat));
JsonObject loanRepaymentJsonob = gsonBuilder.create().toJsonTree(loanRepayments.get(rowIndex)).getAsJsonObject();
loanRepaymentJsonob.remove("manuallyReversed");
loanRepaymentJsonob.remove("numberOfRepayments");
String payload = loanRepaymentJsonob.toString();
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
.loanRepaymentTransaction(result.getLoanId()) //
@@ -32,6 +32,7 @@ public interface LoanApiConstants {
String principalDisbursedParameterName = "transactionAmount";
String chargesParameterName = "charges";
String loanIdTobeApproved = "loanId";
String postDatedChecks = "postDatedChecks";
String approvedLoanAmountParameterName = "approvedLoanAmount";
String approvedOnDateParameterName = "approvedOnDate";
@@ -30,6 +30,7 @@ import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -54,6 +55,7 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSeria
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -132,6 +134,10 @@ public class LoanTransactionsApiResource {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (is(commandParam, "disburse")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
transactionData.setNumberOfRepayments(this.loanReadPlatformService.retrieveNumberOfRepayments(loanId));
final List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallmentData = this.loanReadPlatformService
.getRepaymentDataResponse(loanId);
transactionData.setLoanRepaymentScheduleInstallments(loanRepaymentScheduleInstallmentData);
} else if (is(commandParam, "disburseToSavings")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false);
} else if (is(commandParam, "recoverypayment")) {
@@ -0,0 +1,47 @@
/**
* 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.data;
import java.math.BigDecimal;
import java.time.LocalDate;
public final class LoanRepaymentScheduleInstallmentData {
private Long id;
private Integer installmentId;
private LocalDate date;
private BigDecimal amount;
private LoanRepaymentScheduleInstallmentData(final Long id, final Integer installmentId, final LocalDate date,
final BigDecimal amount) {
this.amount = amount;
this.date = date;
this.installmentId = installmentId;
this.id = id;
}
public static LoanRepaymentScheduleInstallmentData instanceOf(final Long id, final Integer installmentId, final LocalDate date,
final BigDecimal amount) {
return new LoanRepaymentScheduleInstallmentData(id, installmentId, date, amount);
}
}
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.account.data.AccountTransferData;
@@ -66,6 +67,8 @@ public class LoanTransactionData {
private Collection<CodeValueData> writeOffReasonOptions = null;
private Integer numberOfRepayments = 0;
// import fields
private transient Integer rowIndex;
private String dateFormat;
@@ -80,6 +83,7 @@ public class LoanTransactionData {
private Integer bankNumber;
private transient Long accountId;
private transient String transactionType;
private List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallments;
public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
Integer rowIndex, String locale, String dateFormat) {
@@ -167,6 +171,14 @@ public class LoanTransactionData {
this.writeOffReasonOptions = null;
}
public void setNumberOfRepayments(Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
}
public void setLoanRepaymentScheduleInstallments(final List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallments) {
this.loanRepaymentScheduleInstallments = loanRepaymentScheduleInstallments;
}
public Long getAccountId() {
return accountId;
}
@@ -143,6 +143,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyTy
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.rate.domain.Rate;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
import org.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -322,7 +323,7 @@ public class Loan extends AbstractPersistableCustom {
private Set<LoanOfficerAssignmentHistory> loanOfficerHistory;
@OrderBy(value = "installmentNumber")
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.EAGER)
private List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = new ArrayList<>();
@OrderBy(value = "dateOf, id")
@@ -358,6 +359,9 @@ public class Loan extends AbstractPersistableCustom {
@OrderBy(value = "expectedDisbursementDate, id")
private List<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.EAGER)
private List<PostDatedChecks> postDatedChecks = new ArrayList<>();
@OrderBy(value = "termApplicableFrom, id")
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
private List<LoanTermVariations> loanTermVariations = new ArrayList<>();
@@ -528,6 +532,10 @@ public class Loan extends AbstractPersistableCustom {
}
public Integer getNumberOfRepayments() {
return this.loanRepaymentScheduleDetail.getNumberOfRepayments();
}
private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
if (this.summary == null) {
this.summary = LoanSummary.create(feeChargesDueAtDisbursement);
@@ -2821,6 +2829,11 @@ public class Loan extends AbstractPersistableCustom {
return chargesPayment;
}
public void removePostDatedChecks() {
List<PostDatedChecks> postDatedChecks = new ArrayList<>();
this.postDatedChecks = postDatedChecks;
}
public Map<String, Object> undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, AppUser currentUser) {
@@ -6267,6 +6280,14 @@ public class Loan extends AbstractPersistableCustom {
return amount;
}
public void updatePostDatedChecks(final List<PostDatedChecks> postDatedChecks) {
this.postDatedChecks = postDatedChecks;
}
public List<PostDatedChecks> getPostDatedChecks() {
return this.postDatedChecks;
}
public void updateWriteOffReason(CodeValue writeOffReason) {
this.writeOffReason = writeOffReason;
}
@@ -57,6 +57,7 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstr
import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEntity;
@@ -74,6 +75,9 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.note.domain.Note;
import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
@@ -102,6 +106,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanUtilService loanUtilService;
private final StandingInstructionRepository standingInstructionRepository;
private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanCollateralManagementRepository loanCollateralManagementRepository;
@Autowired
@@ -116,10 +121,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context,
final BusinessEventNotifierService businessEventNotifierService, final LoanUtilService loanUtilService,
final StandingInstructionRepository standingInstructionRepository,
final StandingInstructionRepository standingInstructionRepository, final PostDatedChecksRepository postDatedChecksRepository,
final LoanCollateralManagementRepository loanCollateralManagementRepository) {
this.loanAccountAssembler = loanAccountAssembler;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.loanTransactionRepository = loanTransactionRepository;
this.noteRepository = noteRepository;
this.configurationDomainService = configurationDomainService;
@@ -135,7 +139,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.businessEventNotifierService = businessEventNotifierService;
this.loanUtilService = loanUtilService;
this.standingInstructionRepository = standingInstructionRepository;
this.postDatedChecksRepository = postDatedChecksRepository;
this.loanCollateralManagementRepository = loanCollateralManagementRepository;
this.loanRepositoryWrapper = loanRepositoryWrapper;
}
@Transactional
@@ -246,6 +252,35 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
builderResult.withEntityId(newRepaymentTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId())
.withGroupId(loan.getGroupId());
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
// Mark Post Dated Check as paid.
final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = newRepaymentTransaction
.getLoanTransactionToRepaymentScheduleMappings();
if (loanTransactionToRepaymentScheduleMappings != null) {
for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : loanTransactionToRepaymentScheduleMappings) {
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
.getLoanRepaymentScheduleInstallment();
if (loanRepaymentScheduleInstallment != null) {
final boolean isPaid = loanRepaymentScheduleInstallment.isNotFullyPaidOff();
PostDatedChecks postDatedChecks = this.postDatedChecksRepository
.getPendingPostDatedCheck(loanRepaymentScheduleInstallment);
if (postDatedChecks != null) {
if (!isPaid) {
postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PAID);
} else {
postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PENDING);
}
this.postDatedChecksRepository.saveAndFlush(postDatedChecks);
} else {
break;
}
}
}
}
}
return newRepaymentTransaction;
}
@@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
@Entity
@Table(name = "m_loan_repayment_schedule")
@@ -137,6 +138,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
@JoinColumn(name = "loan_repayment_schedule_id", referencedColumnName = "id", nullable = false)
private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
private Set<PostDatedChecks> postDatedChecks;
LoanRepaymentScheduleInstallment() {
this.installmentNumber = null;
this.fromDate = null;
@@ -212,6 +216,14 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return fromLocalDate;
}
public void setPostDatedChecksToNull() {
this.postDatedChecks = null;
}
public Set<PostDatedChecks> getPostDatedCheck() {
return this.postDatedChecks;
}
public LocalDate getDueDate() {
return LocalDate.ofInstant(this.dueDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
}
@@ -111,6 +111,16 @@ public class LoanRepositoryWrapper {
return this.repository.getMaxClientOrJLGLoanCounter(clientId);
}
public List<LoanRepaymentScheduleInstallment> getLoanRepaymentScheduleInstallments(final Long loanId) {
final Loan loan = this.repository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
return loanRepaymentScheduleInstallments;
}
public Integer getNumberOfRepayments(final Long loanId) {
return this.repository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)).getNumberOfRepayments();
}
public Integer getMaxClientOrJLGLoanProductCounter(@Param("productId") Long productId, @Param("clientId") Long clientId) {
return this.repository.getMaxClientOrJLGLoanProductCounter(productId, clientId);
}
@@ -0,0 +1,38 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class LoanRepaymentScheduleNotFoundException extends AbstractPlatformResourceNotFoundException {
public LoanRepaymentScheduleNotFoundException(final Integer id) {
super("error.msg.loan.repayment.installment.id.invalid", "Loan repayment with installment identifier " + id + " does not exist",
id);
}
public LoanRepaymentScheduleNotFoundException(final Long id) {
super("error.msg.loan.repayment.id.invalid", "Loan repayment with identifier " + id + " does not exist", id);
}
public LoanRepaymentScheduleNotFoundException(final Long id, final Integer loan) {
super("error.msg.loan.id" + id + ".not.contains.repayment.schedule", "Loan does not have repayment scheduled", loan);
}
}
@@ -18,7 +18,9 @@
*/
package org.apache.fineract.portfolio.loanaccount.serialization;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
@@ -30,18 +32,25 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -50,12 +59,14 @@ public final class LoanEventApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper;
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
private final LoanRepository loanRepository;
@Autowired
public LoanEventApiJsonValidator(final FromJsonHelper fromApiJsonHelper,
final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer) {
final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, final LoanRepository loanRepository) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanRepository = loanRepository;
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -79,7 +90,8 @@ public final class LoanEventApiJsonValidator {
} else {
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
"paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate",
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName));
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName,
LoanApiConstants.postDatedChecks));
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
@@ -110,6 +122,52 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateDisbursementWithPostDatedChecks(final String json, final Long loanId) {
final JsonElement jsonElement = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.disbursement");
final Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallment = loan.getRepaymentScheduleInstallments();
JsonObject jsonObject = jsonElement.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(jsonObject);
if (jsonObject.has("postDatedChecks") && jsonObject.get("postDatedChecks").isJsonArray()) {
JsonArray postDatedChecks = jsonObject.get("postDatedChecks").getAsJsonArray();
for (int i = 0; i < postDatedChecks.size(); i++) {
final JsonObject postDatedCheck = postDatedChecks.get(i).getAsJsonObject();
final String name = this.fromApiJsonHelper.extractStringNamed("name", postDatedCheck);
baseDataValidator.reset().parameter("name").value(name).notNull();
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", postDatedCheck, locale);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", postDatedCheck);
baseDataValidator.reset().parameter("accountNo").value(accountNo).notNull().positiveAmount();
final Long checkNo = this.fromApiJsonHelper.extractLongNamed("checkNo", postDatedCheck);
baseDataValidator.reset().parameter("checkNo").value(checkNo).notNull().positiveAmount();
final Integer installmentId = this.fromApiJsonHelper.extractIntegerNamed("installmentId", postDatedCheck, locale);
final List<LoanRepaymentScheduleInstallment> installmentList = loanRepaymentScheduleInstallment.stream().filter(
repayment -> repayment.getInstallmentNumber().equals(installmentId) && repayment.getLoan().getId().equals(loanId))
.collect(Collectors.toList());
if (installmentList.size() > 1) {
throw new PlatformDataIntegrityException("error.repayment.redundancy", "Multiple installment data found",
"postDatedChecks");
} else if (installmentList.size() == 0) {
throw new LoanRepaymentScheduleNotFoundException(installmentId);
}
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
}
public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance,
Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
if (null != calendarInstance) {
@@ -31,6 +31,7 @@ import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
@@ -140,6 +141,10 @@ public interface LoanReadPlatformService {
Integer retrieveNumberOfActiveLoans();
Integer retrieveNumberOfRepayments(Long loanId);
List<LoanAccountData> retrieveGLIMChildLoansByGLIMParentAccount(String parentloanAccountNumber);
List<LoanRepaymentScheduleInstallmentData> getRepaymentDataResponse(Long loanId);
}
@@ -86,6 +86,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
@@ -105,6 +106,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
@@ -535,12 +537,38 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
if (paymentDetailsRequired) {
paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
}
return LoanTransactionData.loanTransactionDataForDisbursalTemplate(transactionType,
loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(), paymentOptions,
loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling());
}
@Override
public Integer retrieveNumberOfRepayments(final Long loanId) {
this.context.authenticatedUser();
return this.loanRepositoryWrapper.getNumberOfRepayments(loanId);
}
@Override
public List<LoanRepaymentScheduleInstallmentData> getRepaymentDataResponse(final Long loanId) {
this.context.authenticatedUser();
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = this.loanRepositoryWrapper
.getLoanRepaymentScheduleInstallments(loanId);
List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallmentData = new ArrayList<>();
if (loanRepaymentScheduleInstallments == null || loanRepaymentScheduleInstallments.size() == 0) {
throw new LoanRepaymentScheduleNotFoundException(loanId, Integer.valueOf(0));
}
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loanRepaymentScheduleInstallments) {
loanRepaymentScheduleInstallmentData.add(LoanRepaymentScheduleInstallmentData.instanceOf(
loanRepaymentScheduleInstallment.getId(), loanRepaymentScheduleInstallment.getInstallmentNumber(),
loanRepaymentScheduleInstallment.getDueDate(), loanRepaymentScheduleInstallment
.getTotalOutstanding(loanRepaymentScheduleInstallment.getLoan().getCurrency()).getAmount()));
}
return loanRepaymentScheduleInstallmentData;
}
@Override
public LoanTransactionData retrieveLoanTransaction(final Long loanId, final Long transactionId) {
this.context.authenticatedUser();
@@ -193,6 +193,9 @@ import org.apache.fineract.portfolio.note.domain.Note;
import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.transfer.api.TransferApiConstants;
import org.apache.fineract.useradministration.domain.AppUser;
@@ -249,6 +252,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final CashierTransactionDataValidator cashierTransactionDataValidator;
private final GLIMAccountInfoRepository glimRepository;
private final LoanRepository loanRepository;
private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler;
private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanChargePaidByRepository loanChargePaidByRepository;
@Autowired
@@ -279,7 +284,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy,
final CodeValueRepositoryWrapper codeValueRepository, final LoanRepositoryWrapper loanRepositoryWrapper,
final CashierTransactionDataValidator cashierTransactionDataValidator, final GLIMAccountInfoRepository glimRepository,
final LoanRepository loanRepository, final LoanChargePaidByRepository loanChargePaidByRepository) {
final LoanRepository loanRepository, final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler,
final PostDatedChecksRepository postDatedChecksRepository, final LoanChargePaidByRepository loanChargePaidByRepository) {
this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler;
@@ -320,6 +326,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.cashierTransactionDataValidator = cashierTransactionDataValidator;
this.loanRepository = loanRepository;
this.glimRepository = glimRepository;
this.repaymentWithPostDatedChecksAssembler = repaymentWithPostDatedChecksAssembler;
this.postDatedChecksRepository = postDatedChecksRepository;
this.loanChargePaidByRepository = loanChargePaidByRepository;
}
@@ -359,6 +367,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanEventApiJsonValidator.validateDisbursement(command.json(), isAccountTransfer);
if (command.parameterExists("postDatedChecks")) {
// validate with post dated checks for the disbursement
this.loanEventApiJsonValidator.validateDisbursementWithPostDatedChecks(command.json(), loanId);
}
final Loan loan = this.loanAssembler.assembleFrom(loanId);
// Get disbursedAmount
@@ -541,6 +554,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
updateRecurringCalendarDatesForInterestRecalculation(loan);
this.loanAccountDomainService.recalculateAccruals(loan);
// Post Dated Checks
if (command.parameterExists("postDatedChecks")) {
// get repayment with post dates checks to update
Set<PostDatedChecks> postDatedChecks = this.repaymentWithPostDatedChecksAssembler.fromParsedJson(command.json(), loan);
updatePostDatedChecks(postDatedChecks);
}
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.LOAN_DISBURSAL,
constructEntityMap(BusinessEntity.LOAN, loan));
return new CommandProcessingResultBuilder() //
@@ -554,6 +575,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build();
}
private void updatePostDatedChecks(Set<PostDatedChecks> postDatedChecks) {
this.postDatedChecksRepository.saveAll(postDatedChecks);
}
private void createAndSaveLoanScheduleArchive(final Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO) {
LoanRescheduleRequest loanRescheduleRequest = null;
LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO);
@@ -623,6 +648,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
try {
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) {
// installment.setPostDatedChecksToNull();
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
}
@@ -840,6 +866,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.setActualDisbursementDate(null);
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
// Remove post dated checks if added.
loan.removePostDatedChecks();
final Map<String, Object> changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds,
currentUser);
@@ -0,0 +1,106 @@
/**
* 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.repaymentwithpostdatedchecks.api;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.LocalDate;
final class PostDatedChecksApiResourceSwagger {
private PostDatedChecksApiResourceSwagger() {}
@Schema(description = "GetPostDatedChecks")
public static final class GetPostDatedChecks {
private GetPostDatedChecks() {}
@Schema(example = "1")
public Long id;
@Schema(example = "1")
public Integer installmentId;
@Schema(example = "AMANA")
public String name;
@Schema(example = "900800200300")
public Long accountNo;
@Schema(example = "100000")
public BigDecimal amount;
@Schema(example = "2021-07-18")
public LocalDate date;
}
@Schema(description = "UpdatePostDatedCheckRequest")
public static final class UpdatePostDatedCheckRequest {
private UpdatePostDatedCheckRequest() {}
@Schema(example = "en")
public String locale;
@Schema(example = "10000")
public BigDecimal amount;
@Schema(example = "900800300400")
public Long accountNo;
@Schema(example = "2021-07-19")
public LocalDate repaymentDate;
@Schema(example = "AMANA")
public String name;
@Schema(example = "dd MM YYYY")
public String dateFormat;
@Schema(example = "2021-08-10")
public LocalDate date;
}
@Schema(description = "UpdatePostDatedCheckResponse")
public static final class UpdatePostDatedCheckResponse {
private UpdatePostDatedCheckResponse() {}
@Schema(example = "12")
public Integer resourceId;
public UpdateChangesResponse changes;
static final class UpdateChangesResponse {
private UpdateChangesResponse() {}
@Schema(example = "10000")
public BigDecimal amount;
@Schema(example = "AMANA")
public String bankName;
@Schema(example = "900800500600")
public Long accountNo;
@Schema(example = "2021-08-10")
public LocalDate date;
}
}
@Schema(description = "DeletePostDatedCheck")
public static final class DeletePostDatedCheck {
private DeletePostDatedCheck() {}
@Schema(example = "1")
public Long loanId;
@Schema(example = "2")
public Long resourceId;
}
}
@@ -0,0 +1,147 @@
/**
* 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.repaymentwithpostdatedchecks.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksData;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/loans/{loanId}/postdatedchecks")
@Component
@Scope("singleton")
@Tag(name = "repayment with post dated checks", description = "Repay with post dated checks")
public class RepaymentWithPostDatedChecksApiResource {
private final PlatformSecurityContext context;
private final FromJsonHelper fromJsonHelper;
private final DefaultToApiJsonSerializer<PostDatedChecksData> apiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final RepaymentWithPostDatedChecksReadPlatformService repaymentWithPostDatedChecksReadPlatformService;
@Autowired
public RepaymentWithPostDatedChecksApiResource(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
final DefaultToApiJsonSerializer<PostDatedChecksData> apiJsonSerializer,
final PortfolioCommandSourceWritePlatformService portfolioCommandSourceWritePlatformService,
final RepaymentWithPostDatedChecksReadPlatformService repaymentWithPostDatedChecksReadPlatformService) {
this.context = context;
this.fromJsonHelper = fromJsonHelper;
this.apiJsonSerializer = apiJsonSerializer;
this.commandsSourceWritePlatformService = portfolioCommandSourceWritePlatformService;
this.repaymentWithPostDatedChecksReadPlatformService = repaymentWithPostDatedChecksReadPlatformService;
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Get All Post Dated Checks", description = "Get All Post dated Checks")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.GetPostDatedChecks.class)))) })
public String getPostDatedChecks(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId) {
this.context.authenticatedUser();
final List<PostDatedChecksData> postDatedChecksDataList = this.repaymentWithPostDatedChecksReadPlatformService
.getPostDatedChecks(loanId);
return this.apiJsonSerializer.serialize(postDatedChecksDataList);
}
@GET
@Path("{installmentId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Get Post Dated Check", description = "Get Post Dated Check")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.GetPostDatedChecks.class)))) })
public String getPostDatedCheck(@PathParam("installmentId") @Parameter(description = "installmentId") final Integer installmentId,
@PathParam("loanId") @Parameter(description = "loanId") final Long loanId) {
this.context.authenticatedUser();
final PostDatedChecksData postDatedChecksData = this.repaymentWithPostDatedChecksReadPlatformService
.getPostDatedCheckByInstallmentId(installmentId, loanId);
return this.apiJsonSerializer.serialize(postDatedChecksData);
}
@PUT
@Path("{postDatedCheckId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update Post Dated Check, Bounced Check", description = "Update Post Dated Check, Bounced Check")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.UpdatePostDatedCheckRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.UpdatePostDatedCheckResponse.class)))) })
public String updatePostDatedChecks(@PathParam("postDatedCheckId") @Parameter(description = "postDatedCheckId") final Long id,
@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("editType") @Parameter(description = "editType") final String type) {
CommandWrapper commandRequest = null;
if ("update".equals(type)) {
commandRequest = new CommandWrapperBuilder().updatePostDatedCheck(id, loanId).withJson(apiRequestBodyAsJson).build();
} else if ("bounced".equals(type)) {
commandRequest = new CommandWrapperBuilder().bouncedCheck(id, loanId).withJson(apiRequestBodyAsJson).build();
}
final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.apiJsonSerializer.serialize(commandProcessingResult);
}
@DELETE
@Path("{postDatedCheckId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete Post Dated Check", description = "Delete Post Dated Check")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.DeletePostDatedCheck.class)))) })
public String deletePostDatedCheck(@PathParam("postDatedCheckId") @Parameter(description = "postDatedCheckId") final Long id,
@PathParam("loanId") @Parameter(description = "loanId") final Long loanId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deletePostDatedCheck(id, loanId).build();
final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.apiJsonSerializer.serialize(commandProcessingResult);
}
}
@@ -0,0 +1,59 @@
/**
* 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.repaymentwithpostdatedchecks.data;
import java.math.BigDecimal;
import java.time.LocalDate;
public final class PostDatedChecksData {
private final Long id;
private final Integer installmentId;
private final String name;
private final Long accountNo;
private final BigDecimal amount;
private final LocalDate installmentDate;
private final Long checkNo;
private final Integer status;
private PostDatedChecksData(final LocalDate installmentDate, final Long id, final Integer installmentId, final Long accountNo,
final BigDecimal amount, final String name, final Long checkNo, final Integer status) {
this.accountNo = accountNo;
this.amount = amount;
this.name = name;
this.id = id;
this.installmentDate = installmentDate;
this.installmentId = installmentId;
this.checkNo = checkNo;
this.status = status;
}
public static PostDatedChecksData from(final LocalDate installmentDate, final Long id, final Integer installmentId,
final Long accountNo, final BigDecimal amount, final String name, final Long checkNo, final Integer status) {
return new PostDatedChecksData(installmentDate, id, installmentId, accountNo, amount, name, checkNo, status);
}
}
@@ -0,0 +1,26 @@
/**
* 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.repaymentwithpostdatedchecks.data;
public interface PostDatedChecksStatus {
Integer POST_DATED_CHECKS_BOUNCED = 1;
Integer POST_DATED_CHECKS_PAID = 2;
Integer POST_DATED_CHECKS_PENDING = 0;
}
@@ -0,0 +1,150 @@
/**
* 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.repaymentwithpostdatedchecks.domain;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@Entity
@Table(name = "m_repayment_with_post_dated_checks")
public class PostDatedChecks extends AbstractPersistableCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", referencedColumnName = "id", nullable = false)
private Loan loan;
@OneToOne(optional = false)
@JoinColumn(name = "repayment_id", referencedColumnName = "id", nullable = false)
private LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment;
@Column(name = "account_no", nullable = false, length = 10)
private Long accountNo;
@Column(name = "bank_name", nullable = false, length = 100)
private String bankName;
@Column(name = "amount", scale = 6, precision = 19)
private BigDecimal amount;
@Column(name = "repayment_date", nullable = false)
private Date repaymentDate;
@Column(name = "status", columnDefinition = "0")
private Integer status;
@Column(name = "check_no", nullable = false, unique = true)
private Long checkNo;
public PostDatedChecks() {}
private PostDatedChecks(final Long accountNo, final String bankName, final BigDecimal amount,
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final Date date, final Loan loan, final Long checkNo) {
this.bankName = bankName;
this.accountNo = accountNo;
this.amount = amount;
this.loanRepaymentScheduleInstallment = loanRepaymentScheduleInstallment;
this.repaymentDate = date;
this.loan = loan;
this.checkNo = checkNo;
}
public static PostDatedChecks instanceOf(final Long accountNo, final String bankName, final BigDecimal amount,
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final Loan loan, final Long checkNo) {
return new PostDatedChecks(accountNo, bankName, amount, loanRepaymentScheduleInstallment,
Date.from(loanRepaymentScheduleInstallment.getDueDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
loan, checkNo);
}
public void setLoan(Loan loan) {
this.loan = loan;
}
public void setLoanRepaymentScheduleInstallment(final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
this.loanRepaymentScheduleInstallment = loanRepaymentScheduleInstallment;
}
public Map<String, Object> updatePostDatedChecks(JsonCommand command) {
final Map<String, Object> changes = new HashMap<>();
if (command.isChangeInBigDecimalParameterNamed("amount", this.amount)) {
final BigDecimal newAmount = command.bigDecimalValueOfParameterNamed("amount");
this.amount = newAmount;
changes.put("amount", newAmount);
}
if (command.isChangeInStringParameterNamed("name", this.bankName)) {
final String newName = command.stringValueOfParameterNamed("name");
this.bankName = newName;
changes.put("bankName", newName);
}
if (command.isChangeInLongParameterNamed("accountNo", this.accountNo)) {
final Long newAccountNo = command.longValueOfParameterNamed("accountNo");
this.accountNo = newAccountNo;
changes.put("accountNo", newAccountNo);
}
if (command.isChangeInLongParameterNamed("checkNo", this.accountNo)) {
final Long newCheckNo = command.longValueOfParameterNamed("checkNo");
this.checkNo = newCheckNo;
changes.put("checkNo", newCheckNo);
}
return changes;
}
public Loan getLoan() {
return this.loan;
}
public LoanRepaymentScheduleInstallment getLoanRepaymentScheduleInstallment() {
return this.loanRepaymentScheduleInstallment;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getBankName() {
return this.bankName;
}
public Long getCheckNo() {
return this.checkNo;
}
public Long getAccountNo() {
return this.accountNo;
}
public BigDecimal getAmount() {
return this.amount;
}
public Integer getStatus() {
return this.status;
}
}
@@ -0,0 +1,33 @@
/**
* 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.repaymentwithpostdatedchecks.domain;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface PostDatedChecksRepository extends JpaRepository<PostDatedChecks, Long>, JpaSpecificationExecutor<PostDatedChecks> {
@Query("select pdc from PostDatedChecks pdc where pdc.status = 0 and pdc.loanRepaymentScheduleInstallment = :loanRepaymentScheduleInstallment")
PostDatedChecks getPendingPostDatedCheck(
@Param("loanRepaymentScheduleInstallment") LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment);
}
@@ -0,0 +1,29 @@
/**
* 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.repaymentwithpostdatedchecks.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class PostDatedCheckBouncedCheckInvalid extends AbstractPlatformDomainRuleException {
public PostDatedCheckBouncedCheckInvalid(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.post.dated.checkNo..invalid", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,33 @@
/**
* 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.repaymentwithpostdatedchecks.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class PostDatedCheckNotFoundException extends AbstractPlatformResourceNotFoundException {
public PostDatedCheckNotFoundException(final Long id) {
super("error.msg.post.dated.check.id.invalid", "Post Dated Check with identifier " + id + " does not exist", id);
}
public PostDatedCheckNotFoundException(final Long id, final Integer installmentNo) {
super("error.msg.post.dated.check.for.installment.not.found",
"Post Dated Check for loan id " + id + " and installment number " + installmentNo + " does not exist");
}
}
@@ -0,0 +1,47 @@
/**
* 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.repaymentwithpostdatedchecks.handler;
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.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@CommandType(entity = "REPAYMENT_WITH_POSTDATEDCHECKS", action = "BOUNCE")
public class BouncePostDatedCheckHandler implements NewCommandSourceHandler {
private final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService;
@Autowired
public BouncePostDatedCheckHandler(
final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService) {
this.repaymentWithPostDatedChecksWritePlatformService = repaymentWithPostDatedChecksWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.repaymentWithPostDatedChecksWritePlatformService.bouncePostDatedChecks(command);
}
}
@@ -0,0 +1,47 @@
/**
* 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.repaymentwithpostdatedchecks.handler;
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.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@CommandType(entity = "REPAYMENT_WITH_POSTDATEDCHECKS", action = "DELETE")
public class DeletePostDatedChecksHandler implements NewCommandSourceHandler {
private final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService;
@Autowired
public DeletePostDatedChecksHandler(
final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService) {
this.repaymentWithPostDatedChecksWritePlatformService = repaymentWithPostDatedChecksWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.repaymentWithPostDatedChecksWritePlatformService.deletePostDatedChecks(command);
}
}
@@ -0,0 +1,47 @@
/**
* 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.repaymentwithpostdatedchecks.handler;
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.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@CommandType(entity = "REPAYMENT_WITH_POSTDATEDCHECKS", action = "UPDATE")
public class EditPostDatedChecksHandler implements NewCommandSourceHandler {
private final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService;
@Autowired
public EditPostDatedChecksHandler(
final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService) {
this.repaymentWithPostDatedChecksWritePlatformService = repaymentWithPostDatedChecksWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.repaymentWithPostDatedChecksWritePlatformService.updatePostDatedChecks(command);
}
}
@@ -0,0 +1,78 @@
/**
* 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.repaymentwithpostdatedchecks.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RepaymentWithPostDatedChecksAssembler {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public RepaymentWithPostDatedChecksAssembler(final FromJsonHelper fromJsonHelper) {
this.fromApiJsonHelper = fromJsonHelper;
}
public Set<PostDatedChecks> fromParsedJson(final String json, final Loan loan) {
final Set<PostDatedChecks> postDatedChecks = new HashSet<>();
final JsonElement jsonElement = this.fromApiJsonHelper.parse(json);
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
JsonObject jsonObject = jsonElement.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(jsonObject);
if (jsonObject.has("postDatedChecks") && jsonObject.get("postDatedChecks").isJsonArray()) {
JsonArray postDatedCheckArray = jsonObject.get("postDatedChecks").getAsJsonArray();
for (int i = 0; i < postDatedCheckArray.size(); i++) {
final JsonObject postDatedCheck = postDatedCheckArray.get(i).getAsJsonObject();
if (postDatedCheck == null) {
continue;
}
final String name = this.fromApiJsonHelper.extractStringNamed("name", postDatedCheck);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", postDatedCheck, locale);
final Integer installmentId = this.fromApiJsonHelper.extractIntegerNamed("installmentId", postDatedCheck, locale);
final List<LoanRepaymentScheduleInstallment> installmentList = loanRepaymentScheduleInstallments.stream()
.filter(repayment -> repayment.getInstallmentNumber().equals(installmentId)).collect(Collectors.toList());
final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", postDatedCheck);
final Long checkNo = this.fromApiJsonHelper.extractLongNamed("checkNo", postDatedCheck);
postDatedChecks.add(PostDatedChecks.instanceOf(accountNo, name, amount, installmentList.get(0), loan, checkNo));
}
}
return postDatedChecks;
}
}
@@ -0,0 +1,32 @@
/**
* 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.repaymentwithpostdatedchecks.service;
import java.util.List;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksData;
public interface RepaymentWithPostDatedChecksReadPlatformService {
List<PostDatedChecksData> getPostDatedChecks(Long id);
PostDatedChecksData getPostDatedCheck(Long id);
PostDatedChecksData getPostDatedCheckByInstallmentId(Integer id, Long loanId);
}
@@ -0,0 +1,98 @@
/**
* 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.repaymentwithpostdatedchecks.service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksData;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.exception.PostDatedCheckNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RepaymentWithPostDatedChecksReadPlatformServiceImpl implements RepaymentWithPostDatedChecksReadPlatformService {
private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanRepository loanRepository;
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
@Autowired
public RepaymentWithPostDatedChecksReadPlatformServiceImpl(final PostDatedChecksRepository postDatedChecksRepository,
final LoanRepository loanRepository,
final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository) {
this.postDatedChecksRepository = postDatedChecksRepository;
this.loanRepository = loanRepository;
this.loanRepaymentScheduleInstallmentRepository = loanRepaymentScheduleInstallmentRepository;
}
@Override
public List<PostDatedChecksData> getPostDatedChecks(final Long id) {
final Loan loan = this.loanRepository.findById(id).orElseThrow(() -> new LoanNotFoundException(id));
final List<PostDatedChecks> postDatedChecks = loan.getPostDatedChecks();
List<PostDatedChecksData> postDatedChecksDataList = new ArrayList<>();
for (PostDatedChecks postDatedCheckObject : postDatedChecks) {
// Avoid bounced checks
if (!PostDatedChecksStatus.POST_DATED_CHECKS_BOUNCED.equals(postDatedCheckObject.getStatus())) {
postDatedChecksDataList.add(PostDatedChecksData.from(
postDatedCheckObject.getLoanRepaymentScheduleInstallment().getDueDate(), postDatedCheckObject.getId(),
postDatedCheckObject.getLoanRepaymentScheduleInstallment().getInstallmentNumber(),
postDatedCheckObject.getAccountNo(), postDatedCheckObject.getAmount(), postDatedCheckObject.getBankName(),
postDatedCheckObject.getCheckNo(), postDatedCheckObject.getStatus()));
}
}
return postDatedChecksDataList;
}
@Override
public PostDatedChecksData getPostDatedCheck(final Long id) {
final PostDatedChecks postDatedChecks = this.postDatedChecksRepository.findById(id)
.orElseThrow(() -> new PostDatedCheckNotFoundException(id));
return PostDatedChecksData.from(postDatedChecks.getLoanRepaymentScheduleInstallment().getDueDate(), postDatedChecks.getId(),
postDatedChecks.getLoanRepaymentScheduleInstallment().getInstallmentNumber(), postDatedChecks.getAccountNo(),
postDatedChecks.getAmount(), postDatedChecks.getBankName(), postDatedChecks.getCheckNo(), postDatedChecks.getStatus());
}
@Override
public PostDatedChecksData getPostDatedCheckByInstallmentId(final Integer id, final Long loanId) {
final Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
final List<PostDatedChecks> postDatedChecks = loan.getPostDatedChecks();
if (postDatedChecks == null || postDatedChecks.size() == 0) {
throw new PostDatedCheckNotFoundException(loanId, id);
}
final PostDatedChecks postDatedChecksData = postDatedChecks.stream()
.filter((postDatedCheck) -> postDatedCheck.getLoanRepaymentScheduleInstallment().getInstallmentNumber().equals(id)
&& !PostDatedChecksStatus.POST_DATED_CHECKS_BOUNCED.equals(postDatedCheck.getStatus()))
.collect(Collectors.toList()).get(0);
return PostDatedChecksData.from(postDatedChecksData.getLoanRepaymentScheduleInstallment().getDueDate(), postDatedChecksData.getId(),
postDatedChecksData.getLoanRepaymentScheduleInstallment().getInstallmentNumber(), postDatedChecksData.getAccountNo(),
postDatedChecksData.getAmount(), postDatedChecksData.getBankName(), postDatedChecksData.getCheckNo(),
postDatedChecksData.getStatus());
}
}
@@ -0,0 +1,31 @@
/**
* 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.repaymentwithpostdatedchecks.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface RepaymentWithPostDatedChecksWritePlatformService {
CommandProcessingResult deletePostDatedChecks(JsonCommand command);
CommandProcessingResult updatePostDatedChecks(JsonCommand command);
CommandProcessingResult bouncePostDatedChecks(JsonCommand command);
}
@@ -0,0 +1,227 @@
/**
* 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.repaymentwithpostdatedchecks.service;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.exception.PostDatedCheckNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RepaymentWithPostDatedChecksWritePlatformServiceImpl implements RepaymentWithPostDatedChecksWritePlatformService {
private final PostDatedChecksRepository postDatedChecksRepository;
private final FromJsonHelper fromApiJsonHelper;
private final LoanRepository loanRepository;
/**
* The parameters supported for this command.
*/
private final Set<String> supportedParametersForUpdate = new HashSet<>(Arrays.asList("name", "amount", "accountNo", "locale"));
private final Set<String> supportedParametersForBounce = new HashSet<>(
Arrays.asList("name", "amount", "accountNo", "checkNo", "locale", "installmentId"));
@Autowired
public RepaymentWithPostDatedChecksWritePlatformServiceImpl(final PostDatedChecksRepository postDatedChecksRepository,
final FromJsonHelper fromApiJsonHelper, final LoanRepository loanRepository) {
this.postDatedChecksRepository = postDatedChecksRepository;
this.fromApiJsonHelper = fromApiJsonHelper;
this.loanRepository = loanRepository;
}
@Transactional
@Override
public CommandProcessingResult updatePostDatedChecks(JsonCommand command) {
validateForUpdate(command);
final PostDatedChecks postDatedChecks = this.postDatedChecksRepository.findById(command.entityId())
.orElseThrow(() -> new PostDatedCheckNotFoundException(command.entityId()));
Map<String, Object> changes = postDatedChecks.updatePostDatedChecks(command);
this.postDatedChecksRepository.saveAndFlush(postDatedChecks);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(command.entityId()).with(changes)
.build();
}
@Transactional
@Override
public CommandProcessingResult bouncePostDatedChecks(JsonCommand command) {
validateForBounce(command);
final PostDatedChecks postDatedCheck = this.postDatedChecksRepository.findById(command.entityId())
.orElseThrow(() -> new PostDatedCheckNotFoundException(command.entityId()));
postDatedCheck.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_BOUNCED);
final Loan loan = this.loanRepository.findById(command.getLoanId())
.orElseThrow(() -> new LoanNotFoundException(command.getLoanId()));
final JsonElement jsonElement = this.fromApiJsonHelper.parse(command.json());
JsonObject jsonObject = jsonElement.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(jsonObject);
final String name = this.fromApiJsonHelper.extractStringNamed("name", jsonObject);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", jsonObject, locale);
final Integer installmentId = this.fromApiJsonHelper.extractIntegerNamed("installmentId", jsonObject, locale);
final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", jsonObject);
final Long checkNo = this.fromApiJsonHelper.extractLongNamed("checkNo", jsonObject);
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
final List<LoanRepaymentScheduleInstallment> installmentList = loanRepaymentScheduleInstallments.stream()
.filter(repayment -> repayment.getInstallmentNumber().equals(installmentId)).collect(Collectors.toList());
final PostDatedChecks postDatedChecks = PostDatedChecks.instanceOf(accountNo, name, amount, installmentList.get(0), loan, checkNo);
try {
this.postDatedChecksRepository.saveAndFlush(postDatedChecks);
} catch (final JpaSystemException | DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("postdatedChecks");
if (realCause.getMessage().toLowerCase().contains("transaction has been rolled back")) {
baseDataValidator.reset().parameter("checkNo").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors, e);
}
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(Integer.toUnsignedLong(installmentId))
.withLoanId(postDatedChecks.getLoan().getId()).build();
}
private void validateForBounce(JsonCommand command) {
final JsonElement jsonElement = this.fromApiJsonHelper.parse(command.json());
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, command.json(), this.supportedParametersForBounce);
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("repayment-with-post-dated-checks");
if (!command.parameterExists("locale")) {
baseDataValidator.reset().parameter("locale").notNull().failWithCode("locale.not.exists");
} else {
final String locale = this.fromApiJsonHelper.extractStringNamed("locale", jsonElement);
baseDataValidator.reset().parameter("locale").value(locale).notNull();
}
if (command.parameterExists("amount")) {
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", jsonElement);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
}
if (command.parameterExists("accountNo")) {
final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", jsonElement);
baseDataValidator.reset().parameter("accountNo").value(accountNo).notNull().positiveAmount();
}
if (command.parameterExists("name")) {
final String name = this.fromApiJsonHelper.extractStringNamed("name", jsonElement);
baseDataValidator.reset().parameter("name").value(name).notNull();
}
if (command.parameterExists("checkNo")) {
final Long checkNo = this.fromApiJsonHelper.extractLongNamed("checkNo", jsonElement);
baseDataValidator.reset().parameter("checkNo").value(checkNo).notNull().longGreaterThanZero();
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
private void validateForUpdate(JsonCommand command) {
final JsonElement jsonElement = this.fromApiJsonHelper.parse(command.json());
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, command.json(), this.supportedParametersForUpdate);
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("repayment-with-post-dated-checks");
if (!command.parameterExists("locale")) {
baseDataValidator.reset().parameter("locale").notNull().failWithCode("locale.not.exists");
} else {
final String locale = this.fromApiJsonHelper.extractStringNamed("locale", jsonElement);
baseDataValidator.reset().parameter("locale").value(locale).notNull();
}
if (command.parameterExists("amount")) {
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", jsonElement);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
}
if (command.parameterExists("accountNo")) {
final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", jsonElement);
baseDataValidator.reset().parameter("accountNo").value(accountNo).notNull().positiveAmount();
}
if (command.parameterExists("name")) {
final String name = this.fromApiJsonHelper.extractStringNamed("name", jsonElement);
baseDataValidator.reset().parameter("name").value(name).notNull();
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
@Transactional
@Override
public CommandProcessingResult deletePostDatedChecks(final JsonCommand command) {
this.postDatedChecksRepository.deleteById(command.entityId());
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withLoanId(command.getLoanId())
.withEntityId(command.entityId()).build();
}
}
@@ -0,0 +1,40 @@
--
-- 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.
--
-- create m_repayment_with_post_dated_checks management
CREATE TABLE `m_repayment_with_post_dated_checks` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`check_no` bigint(20) NOT NULL UNIQUE,
`amount` decimal(20,5) NOT NULL,
`loan_id` bigint(20) DEFAULT NULL,
`repayment_id` bigint(20) DEFAULT NULL,
`account_no` bigint(20) NOT NULL,
`bank_name` VARCHAR(200) NOT NULL,
`repayment_date` DATE NOT NULL,
`status` SMALLINT(1) DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
CONSTRAINT `fkloanrepayment` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fkrepaymentwithpostdated` FOREIGN KEY (`repayment_id`) REFERENCES `m_loan_repayment_schedule` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO `m_permission`
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES
('portfolio', 'UPDATE_REPAYMENT_WITH_POSTDATEDCHECKS', 'REPAYMENT_WITH_POSTDATEDCHECKS', 'UPDATE', '0'),
('portfolio', 'DELETE_REPAYMENT_WITH_POSTDATEDCHECKS', 'REPAYMENT_WITH_POSTDATEDCHECKS', 'DELETE', '0'),
('portfolio', 'BOUNCE_REPAYMENT_WITH_POSTDATEDCHECKS', 'REPAYMENT_WITH_POSTDATEDCHECKS', 'BOUNCE', '0');
@@ -0,0 +1,183 @@
/**
* 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 com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
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.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
import org.apache.fineract.integrationtests.common.PaymentTypeDomain;
import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
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.LoanStatusChecker;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class RepaymentWithPostDatedChecksTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private final SimpleDateFormat dateFormatterStandard = new SimpleDateFormat("dd MMMM yyyy");
private LoanTransactionHelper loanTransactionHelper;
@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();
}
@Test
public void testRepaymentWithPostDatedChecks() {
this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
Calendar meetingCalendar = Calendar.getInstance();
meetingCalendar.set(2012, 3, 4);
final String disbursalDate = this.dateFormatterStandard.format(meetingCalendar.getTime());
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
Assertions.assertNotNull(clientID);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder().build(null));
Assertions.assertNotNull(loanProductID, "Could not create Loan Product");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "8000");
Assertions.assertNotNull(loanID, "Could not create Loan Account");
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
// Test for loan account is created, can be approved
this.loanTransactionHelper.approveLoan(disbursalDate, loanID);
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
// Get repayments
final ArrayList<HashMap> installmentData = this.loanTransactionHelper.getRepayments(loanID);
Assertions.assertNotNull(installmentData, "Empty Installment Data");
List<HashMap> postDatedChecks = new ArrayList<>();
Gson gson = new Gson();
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
// Get the first installment date
ArrayList installmentDate = (ArrayList) installmentData.get(0).get("date");
Assertions.assertNotNull(installmentDate);
Assertions.assertEquals(3, installmentDate.size());
Calendar calendar = Calendar.getInstance();
calendar.set((Integer) installmentDate.get(0), (Integer) installmentDate.get(1) - 1, (Integer) installmentDate.get(2));
final String LOAN_REPAYMENT_DATE = dateFormat.format(calendar.getTime());
Float firstInstallmentAmount = (Float) installmentData.get(0).get("amount");
for (int i = 0; i < installmentData.size(); i++) {
String result = gson.toJson(installmentData.get(i));
JsonObject reportObject = JsonParser.parseString(result).getAsJsonObject();
final Integer installmentId = reportObject.get("installmentId").getAsInt();
final BigDecimal amount = reportObject.get("amount").getAsBigDecimal();
postDatedChecks.add(postDatedCheck(installmentId, amount));
}
Assertions.assertNotNull(postDatedChecks);
// Test for loan account approved can be disbursed
this.loanTransactionHelper.disburseLoanWithPostDatedChecks(disbursalDate, loanID, BigDecimal.valueOf(8000), postDatedChecks);
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
// Create payment type PDC - Post Dated Checks
String name = "PDC";
String description = PaymentTypeHelper.randomNameGenerator("PDC", 15);
Boolean isCashPayment = false;
Integer position = 1;
Integer paymentTypeId = PaymentTypeHelper.createPaymentType(requestSpec, responseSpec, name, description, isCashPayment, position);
Assertions.assertNotNull(paymentTypeId);
PaymentTypeHelper.verifyPaymentTypeCreatedOnServer(requestSpec, responseSpec, paymentTypeId);
PaymentTypeDomain paymentTypeResponse = PaymentTypeHelper.retrieveById(requestSpec, responseSpec, paymentTypeId);
Assertions.assertEquals(name, paymentTypeResponse.getName());
// Repay for the installment 1 using post dated check
HashMap postDatedCheck = this.loanTransactionHelper.getPostDatedCheck(loanID, Integer.valueOf(1));
Assertions.assertNotNull(postDatedCheck);
Assertions.assertNotNull(Float.parseFloat(String.valueOf(postDatedCheck.get("amount"))));
this.loanTransactionHelper.makeRepaymentWithPDC(LOAN_REPAYMENT_DATE, firstInstallmentAmount, loanID, paymentTypeId);
}
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String proposedAmount) {
List<HashMap> collaterals = new ArrayList<>();
final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec);
Assertions.assertNotNull(collateralId);
final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec,
clientID.toString(), collateralId);
Assertions.assertNotNull(clientCollateralId);
addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
final String loanApplication = new LoanApplicationTestBuilder().withPrincipal(proposedAmount).withLoanTermFrequency("5")
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("5").withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withExpectedDisbursementDate("04 April 2012")
.withCollaterals(collaterals).withSubmittedOnDate("02 April 2012")
.build(clientID.toString(), loanProductID.toString(), null);
return this.loanTransactionHelper.getLoanId(loanApplication);
}
private void addCollaterals(List<HashMap> collaterals, Integer collateralId, BigDecimal quantity) {
collaterals.add(collaterals(collateralId, quantity));
}
private HashMap<String, String> collaterals(Integer collateralId, BigDecimal quantity) {
HashMap<String, String> collateral = new HashMap<String, String>(2);
collateral.put("clientCollateralId", collateralId.toString());
collateral.put("quantity", quantity.toString());
return collateral;
}
private HashMap<String, String> postDatedCheck(final Integer installmentId, final BigDecimal amount) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("installmentId", installmentId.toString());
map.put("name", "AMANA BANK");
map.put("amount", amount.toString());
map.put("accountNo", "900400500621");
map.put("checkNo", Utils.randomNumberGenerator(12).toString());
return map;
}
}
@@ -28,6 +28,7 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
@@ -165,6 +166,12 @@ public class LoanTransactionHelper {
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, "");
}
public ArrayList<HashMap> getRepayments(final Integer loanId) {
final String GET_REPAYMENTS_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/template?command=disburse&"
+ Utils.TENANT_IDENTIFIER;
return Utils.performServerGet(requestSpec, responseSpec, GET_REPAYMENTS_URL, "loanRepaymentScheduleInstallments");
}
public HashMap approveLoan(final String approvalDate, final Integer loanID) {
String loanApprovalCommand = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID);
String loanApprovalRequest = getApproveLoanAsJSON(approvalDate);
@@ -203,6 +210,28 @@ public class LoanTransactionHelper {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, null));
}
public HashMap disburseLoanWithPostDatedChecks(final String date, final Integer loanId, final BigDecimal transactionAmount,
final List<HashMap> postDatedChecks) {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanId),
getDisburseLoanWithPostDatedChecksAsJSON(date, transactionAmount.toString(), postDatedChecks));
}
private String getDisburseLoanWithPostDatedChecksAsJSON(final String actualDisbursementDate, final String transactionAmount,
final List<HashMap> postDatedChecks) {
final HashMap<String, Object> map = new HashMap<String, Object>();
map.put("locale", "en");
map.put("dateFormat", "dd MMMM yyyy");
map.put("actualDisbursementDate", actualDisbursementDate);
map.put("note", "DISBURSE NOTE");
if (transactionAmount != null) {
map.put("transactionAmount", transactionAmount);
}
map.put("postDatedChecks", postDatedChecks);
LOG.info("Loan Application disburse request : {} ", map);
return new Gson().toJson(map);
}
public HashMap disburseLoanWithRepaymentReschedule(final String date, final Integer loanID, String adjustRepaymentDate) {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID),
getDisburseLoanWithRepaymentRescheduleAsJSON(date, null, adjustRepaymentDate));
@@ -291,6 +320,11 @@ public class LoanTransactionHelper {
getRepaymentBodyAsJSON(date, amountToBePaid), "");
}
public HashMap makeRepaymentWithPDC(final String date, final Float amountToBePaid, final Integer loanID, final Integer paymentType) {
return (HashMap) performLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID),
getRepaymentWithPDCBodyAsJSON(date, amountToBePaid, paymentType), "");
}
public HashMap forecloseLoan(final String transactionDate, final Integer loanID) {
return (HashMap) performLoanTransaction(createLoanTransactionURL(FORECLOSURE_COMMAND, loanID),
getForeclosureBodyAsJSON(transactionDate, loanID), "");
@@ -367,6 +401,12 @@ public class LoanTransactionHelper {
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, "");
}
public HashMap getPostDatedCheck(final Integer loanId, final Integer installmentId) {
final String GET_POST_DATED_TRANS_URL = "/fineract-provider/api/v1/loans/" + loanId + "/postdatedchecks/" + installmentId + "?"
+ Utils.TENANT_IDENTIFIER;
return Utils.performServerGet(requestSpec, responseSpec, GET_POST_DATED_TRANS_URL, "");
}
private String getDisburseLoanAsJSON(final String actualDisbursementDate, final String transactionAmount) {
final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en");
@@ -444,6 +484,17 @@ public class LoanTransactionHelper {
return new Gson().toJson(map);
}
private String getRepaymentWithPDCBodyAsJSON(final String transactionDate, final Float transactionAmount, final Integer paymentTypeId) {
final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en");
map.put("paymentTypeId", paymentTypeId.toString());
map.put("dateFormat", "dd MMMM yyyy");
map.put("transactionDate", transactionDate);
map.put("transactionAmount", transactionAmount.toString());
map.put("note", "Repayment Made!!!");
return new Gson().toJson(map);
}
private String getForeclosureBodyAsJSON(final String transactionDate, final Integer loanId) {
final HashMap<String, Object> map = new HashMap<>();
map.put("locale", "en");