wip: repayment/disbursal apis support optional payment details

This commit is contained in:
Vishwas Babu AJ
2013-04-09 17:00:25 +05:30
parent c2a462aede
commit e15a72eab1
14 changed files with 503 additions and 95 deletions
+27
View File
@@ -0,0 +1,27 @@
/*
New table for storing transaction details (for both loan and savings)
*/
CREATE TABLE `m_payment_detail` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`payment_type_enum` SMALLINT(5) NOT NULL,
`account_number` VARCHAR(100) NULL DEFAULT NULL,
`check_number` VARCHAR(100) NULL DEFAULT NULL,
`receipt_number` VARCHAR(100) NULL DEFAULT NULL,
`bank_number` VARCHAR(100) NULL DEFAULT NULL,
`routing_code` VARCHAR(100) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
/*
Update loan transaction to add a link to transaction detail table
*/
ALTER TABLE `m_loan_transaction`
ADD COLUMN `payment_detail_id` BIGINT(20) NULL AFTER `loan_id`,
ADD CONSTRAINT `FK_m_loan_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`);
@@ -24,6 +24,8 @@ public class LoanTransactionData {
@SuppressWarnings("unused")
private final CurrencyData currency;
@SuppressWarnings("unused")
private final PaymentDetailData paymentDetailData;
@SuppressWarnings("unused")
private final BigDecimal amount;
@@ -36,11 +38,12 @@ public class LoanTransactionData {
@SuppressWarnings("unused")
private final BigDecimal penaltyChargesPortion;
public LoanTransactionData(final Long id, final LoanTransactionEnumData transactionType, final CurrencyData currency,
final LocalDate date, final BigDecimal amount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion) {
public LoanTransactionData(final Long id, final LoanTransactionEnumData transactionType, final PaymentDetailData paymentDetailData,
final CurrencyData currency, final LocalDate date, final BigDecimal amount, final BigDecimal principalPortion,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion) {
this.id = id;
this.type = transactionType;
this.paymentDetailData = paymentDetailData;
this.currency = currency;
this.date = date;
this.amount = amount;
@@ -46,6 +46,17 @@ public class LoanTransactionEnumData {
return this.id;
}
/**
* Returns true if the transaction involves either a payment (disbursement)
* or a receipt (repayments, repayments during disbursement etc)
*
* @return
*/
public boolean isPaymentOrReceipt() {
if (isDisbursement() || isRepayment() || isRepaymentAtDisbursement() || isRecoveryRepayment()) { return true; }
return false;
}
public boolean isDisbursement() {
return this.disbursement;
}
@@ -0,0 +1,41 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.data;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
/**
* Immutable data object representing a payment.
*/
public class PaymentDetailData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final EnumOptionData paymentType;
@SuppressWarnings("unused")
private final String accountNumber;
@SuppressWarnings("unused")
private final String checkNumber;
@SuppressWarnings("unused")
private final String routingCode;
@SuppressWarnings("unused")
private final String receiptNumber;
@SuppressWarnings("unused")
private final String bankNumber;
public PaymentDetailData(Long id, EnumOptionData paymentType, String accountNumber, String checkNumber, String routingCode,
String receiptNumber, String bankNumber) {
this.id = id;
this.paymentType = paymentType;
this.accountNumber = accountNumber;
this.checkNumber = checkNumber;
this.routingCode = routingCode;
this.receiptNumber = receiptNumber;
this.bankNumber = bankNumber;
}
}
@@ -810,7 +810,8 @@ public class Loan extends AbstractPersistable<Long> {
}
final String clientIdParamName = "clientId";
//FIXME AA : Client is null for group loans. Group loan modifications require group validation.
// FIXME AA : Client is null for group loans. Group loan modifications
// require group validation.
if (this.client != null) {
if (command.isChangeInLongParameterNamed(clientIdParamName, this.client.getId())) {
final Long newValue = command.longValueOfParameterNamed(clientIdParamName);
@@ -1227,11 +1228,12 @@ public class Loan extends AbstractPersistable<Long> {
this.actualDisbursementDate = expectedDisbursedOnLocalDate.toDate();
updateLoanScheduleDependentDerivedFields();
/****
* Vishwas TODO: Refactor to skip unnecessary creation of Disbursement
* Vishwas TODO: Re-factor to skip unnecessary creation of Disbursement
* and fees applied transactions (though they are not persisted) on
* modifications to loan application modification
*****/
handleDisbursementTransaction(expectedDisbursedOnLocalDate);
PaymentDetail paymentDetail = null;
handleDisbursementTransaction(paymentDetail, expectedDisbursedOnLocalDate);
}
private Collection<Long> findExistingTransactionIds() {
@@ -1259,9 +1261,8 @@ public class Loan extends AbstractPersistable<Long> {
}
public Map<String, Object> disburse(final AppUser currentUser, final JsonCommand command, final ApplicationCurrency currency,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Map<String, Object> actualChanges, final PaymentDetail paymentDetail) {
updateLoanToPreDisbursalState();
@@ -1284,7 +1285,7 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put("dateFormat", command.dateFormat());
actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
handleDisbursementTransaction(actualDisbursementDate);
handleDisbursementTransaction(paymentDetail, actualDisbursementDate);
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
regenerateRepaymentSchedule(currency);
@@ -1349,19 +1350,24 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanSchedule(generatedData);
}
private LoanTransaction handleDisbursementTransaction(final LocalDate disbursedOn) {
private LoanTransaction handleDisbursementTransaction(final PaymentDetail paymentDetail, final LocalDate disbursedOn) {
// track disbursement transaction
final LoanTransaction disbursementTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(),
disbursedOn);
paymentDetail, disbursedOn);
disbursementTransaction.updateLoan(this);
this.loanTransactions.add(disbursementTransaction);
// add repayment transaction to track incoming money from client to mfi
// for (charges due at time of disbursement)
/***
* TODO Vishwas: do we need to be able to pass in payment type details
* for repayments at disbursements too?
***/
final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency());
if (totalFeeChargesDueAtDisbursement.isGreaterThanZero()) {
LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(totalFeeChargesDueAtDisbursement, disbursedOn);
LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(totalFeeChargesDueAtDisbursement, null, disbursedOn);
Money zero = Money.zero(getCurrency());
chargesPayment.updateComponents(zero, zero, totalFeeChargesDueAtDisbursement, zero);
chargesPayment.updateLoan(this);
@@ -24,6 +24,7 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.mifosplatform.portfolio.loanaccount.data.PaymentDetailData;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.data.jpa.domain.AbstractPersistable;
@@ -39,6 +40,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@ManyToOne(optional = true)
@JoinColumn(name = "payment_detail_id", nullable = true)
private PaymentDetail paymentDetail;
@Column(name = "transaction_type_enum", nullable = false)
private final Integer typeOf;
@@ -70,16 +75,16 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
this.typeOf = null;
}
public static LoanTransaction disbursement(final Money amount, final LocalDate disbursementDate) {
return new LoanTransaction(null, LoanTransactionType.DISBURSEMENT, amount.getAmount(), disbursementDate);
public static LoanTransaction disbursement(final Money amount, final PaymentDetail paymentDetail, final LocalDate disbursementDate) {
return new LoanTransaction(null, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate);
}
public static LoanTransaction repayment(final Money amount, final LocalDate paymentDate) {
return new LoanTransaction(null, LoanTransactionType.REPAYMENT, amount.getAmount(), paymentDate);
public static LoanTransaction repayment(final Money amount, final PaymentDetail paymentDetail, final LocalDate paymentDate) {
return new LoanTransaction(null, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate);
}
public static LoanTransaction repaymentAtDisbursement(final Money amount, final LocalDate paymentDate) {
return new LoanTransaction(null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, amount.getAmount(), paymentDate);
public static LoanTransaction repaymentAtDisbursement(final Money amount, final PaymentDetail paymentDetail, final LocalDate paymentDate) {
return new LoanTransaction(null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(), paymentDate);
}
public static LoanTransaction waiver(final Loan loan, final Money waived, final LocalDate waiveDate) {
@@ -146,6 +151,15 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
this.dateOf = date.toDateMidnight().toDate();
}
private LoanTransaction(final Loan loan, final LoanTransactionType type, final PaymentDetail paymentDetail, final BigDecimal amount,
final LocalDate date) {
this.loan = loan;
this.typeOf = type.getValue();
this.paymentDetail = paymentDetail;
this.amount = amount;
this.dateOf = date.toDateMidnight().toDate();
}
public void reverse() {
this.reversed = true;
}
@@ -315,7 +329,11 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public LoanTransactionData toData(final CurrencyData currencyData) {
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(this.typeOf);
return new LoanTransactionData(this.getId(), transactionType, currencyData, getTransactionDate(), this.amount,
PaymentDetailData paymentDetailData = null;
if (this.paymentDetail != null) {
paymentDetailData = paymentDetail.toData();
}
return new LoanTransactionData(this.getId(), transactionType, paymentDetailData, currencyData, getTransactionDate(), this.amount,
this.principalPortion, this.interestPortion, this.feeChargesPortion, this.penaltyChargesPortion);
}
@@ -0,0 +1,128 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.domain;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.loanaccount.data.PaymentDetailData;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_payment_detail")
public final class PaymentDetail extends AbstractPersistable<Long> {
@Column(name = "payment_type_enum", nullable = false)
private Integer paymentType;
@Column(name = "account_number", length = 50)
private String accountNumber;
@Column(name = "check_number", length = 50)
private String checkNumber;
@Column(name = "routing_code", length = 50)
private String routingCode;
@Column(name = "receipt_number", length = 50)
private String receiptNumber;
@Column(name = "bank_number", length = 50)
private String bankNumber;
protected PaymentDetail() {
}
public static PaymentDetail generatePaymentDetail(final JsonCommand command, final Map<String, Object> changes) {
// create a payment detail
final Integer paymentTypeId = command.integerValueSansLocaleOfParameterNamed("paymentTypeId");
if (paymentTypeId == null) { return null; }
changes.put("paymentTypeId", paymentTypeId);
final String accountNumber = command.stringValueOfParameterNamed("accountNumber");
final String checkNumber = command.stringValueOfParameterNamed("checkNumber");
final String routingCode = command.stringValueOfParameterNamed("routingCode");
final String receiptNumber = command.stringValueOfParameterNamed("receiptNumber");
final String bankNumber = command.stringValueOfParameterNamed("bankNumber");
final PaymentType paymentType = PaymentType.fromInt(paymentTypeId);
PaymentDetail paymentDetail = null;
if (paymentType.isCheckPayment()) {
paymentDetail = PaymentDetail.checkPaymentDetail(accountNumber, checkNumber, routingCode);
if (StringUtils.isNotBlank(accountNumber)) {
changes.put("accountNumber", accountNumber);
}
if (StringUtils.isNotBlank(checkNumber)) {
changes.put("checkNumber", checkNumber);
}
if (StringUtils.isNotBlank(routingCode)) {
changes.put("routingCode", routingCode);
}
} else if (paymentType.isEFTPayment()) {
paymentDetail = PaymentDetail.eftPaymentDetail(accountNumber, routingCode);
if (StringUtils.isNotBlank(accountNumber)) {
changes.put("accountNumber", accountNumber);
}
if (StringUtils.isNotBlank(routingCode)) {
changes.put("routingCode", routingCode);
}
} else if (paymentType.isReceiptPayment()) {
paymentDetail = PaymentDetail.receiptPaymentDetail(receiptNumber, bankNumber);
if (StringUtils.isNotBlank(receiptNumber)) {
changes.put("receiptNumber", receiptNumber);
}
if (StringUtils.isNotBlank(bankNumber)) {
changes.put("bankNumber", bankNumber);
}
} else {
paymentDetail = PaymentDetail.cashPaymentDetail();
}
return paymentDetail;
}
public static PaymentDetail cashPaymentDetail() {
return new PaymentDetail(PaymentType.CASH, null, null, null, null, null);
}
public static PaymentDetail checkPaymentDetail(final String accountNumber, final String checkNumber, final String routingCode) {
return new PaymentDetail(PaymentType.CHECK, accountNumber, checkNumber, routingCode, null, null);
}
public static PaymentDetail receiptPaymentDetail(final String receiptNumber, final String bankNumber) {
return new PaymentDetail(PaymentType.RECEIPT, null, null, null, receiptNumber, bankNumber);
}
public static PaymentDetail eftPaymentDetail(final String accountNumber, final String routingCode) {
return new PaymentDetail(PaymentType.ELECTRONIC_FUND_TRANSFER, accountNumber, null, routingCode, null, null);
}
private PaymentDetail(final PaymentType paymentType, final String accountNumber, final String checkNumber, final String routingCode,
final String receiptNumber, final String bankNumber) {
this.paymentType = paymentType.getValue();
this.accountNumber = accountNumber;
this.checkNumber = checkNumber;
this.routingCode = routingCode;
this.receiptNumber = receiptNumber;
this.bankNumber = bankNumber;
}
public PaymentDetailData toData() {
final EnumOptionData paymentTypeData = LoanEnumerations.paymentType(this.paymentType);
final PaymentDetailData paymentDetailData = new PaymentDetailData(getId(), paymentTypeData, this.accountNumber, this.checkNumber,
this.routingCode, this.receiptNumber, this.bankNumber);
return paymentDetailData;
}
}
@@ -0,0 +1,13 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface PaymentDetailRepository extends JpaRepository<PaymentDetail, Long>, JpaSpecificationExecutor<Loan> {
// no added behaviour
}
@@ -0,0 +1,81 @@
package org.mifosplatform.portfolio.loanaccount.domain;
import java.util.HashMap;
import java.util.Map;
public enum PaymentType {
CASH(1, "paymentType.cash"), CHECK(2, "paymentType.check"), RECEIPT(3, "paymentType.receipt"), //
ELECTRONIC_FUND_TRANSFER(4, "paymentType.eft");// Electronic Funds Transfer
private final Integer value;
private final String code;
private PaymentType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, PaymentType> intToEnumMap = new HashMap<Integer, PaymentType>();
private static int minValue;
private static int maxValue;
static {
int i = 0;
for (final PaymentType type : PaymentType.values()) {
if (i == 0) {
minValue = type.value;
}
intToEnumMap.put(type.value, type);
if (minValue >= type.value) {
minValue = type.value;
}
if (maxValue < type.value) {
maxValue = type.value;
}
i = i + 1;
}
}
public static PaymentType fromInt(final int i) {
final PaymentType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public boolean isCashPayment() {
return this.value.equals(PaymentType.CASH.value);
}
public boolean isCheckPayment() {
return this.value.equals(PaymentType.CHECK.value);
}
public boolean isReceiptPayment() {
return this.value.equals(PaymentType.RECEIPT.value);
}
public boolean isEFTPayment() {
return this.value.equals(PaymentType.ELECTRONIC_FUND_TRANSFER.value);
}
}
@@ -21,6 +21,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanaccount.domain.PaymentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -47,7 +48,7 @@ public final class LoanEventApiJsonValidator {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("actualDisbursementDate", "note", "locale",
"dateFormat"));
"dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
@@ -62,6 +63,8 @@ public final class LoanEventApiJsonValidator {
final String note = fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
validatePaymentDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -69,11 +72,11 @@ public final class LoanEventApiJsonValidator {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "note",
"locale", "dateFormat"));
final Set<String> transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "note",
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
@@ -88,9 +91,28 @@ public final class LoanEventApiJsonValidator {
final String note = fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
validatePaymentDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
/**
* @param baseDataValidator
* @param element
*/
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
// Validate all string payment detail fields for max length
final Integer paymentTypeId = fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
baseDataValidator.reset().parameter("paymentTypeId").value(paymentTypeId)
.inMinMaxRange(PaymentType.getMinValue(), PaymentType.getMaxValue());
final Set<String> paymentDetailParameters = new HashSet<String>(Arrays.asList("accountNumber", "checkNumber", "routingCode",
"receiptNumber", "bankNumber"));
for (String paymentDetailParameterName : paymentDetailParameters) {
final String paymentDetailParameterValue = fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
.notExceedingLengthOf(50);
}
}
public void validateTransactionWithNoAmount(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -35,6 +35,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
import org.mifosplatform.portfolio.loanaccount.data.LoanSummaryData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.mifosplatform.portfolio.loanaccount.data.PaymentDetailData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
@@ -126,6 +127,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
// retrieve all loan transactions that are not invalid and have not
// been 'contra'ed by another transaction
// repayments at time of disbursement (e.g. charges)
/*** TODO Vishwas: Remove references to "Contra" from the codebase ***/
String sql = "select "
+ rm.LoanPaymentsSchema()
+ " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) and tr.is_reversed=0 order by tr.transaction_date ASC";
@@ -158,7 +161,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
context.authenticatedUser();
final GroupData groupAccount = this.groupReadPlatformService.retrieveGroupDetails(groupId , false);
final GroupData groupAccount = this.groupReadPlatformService.retrieveGroupDetails(groupId, false);
final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
LoanAccountData loanDetails = LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate);
@@ -169,10 +172,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
return loanDetails;
}
@Override
public LoanAccountData retrieveTemplateWithCompleteGroupAndProductDetails(Long groupId, Long productId){
public LoanAccountData retrieveTemplateWithCompleteGroupAndProductDetails(Long groupId, Long productId) {
context.authenticatedUser();
final GroupData groupAccount = this.groupReadPlatformService.retrieveGroup(groupId);
@@ -186,8 +189,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
return loanDetails;
}
@Override
public LoanTransactionData retrieveLoanTransactionTemplate(final Long loanId) {
@@ -209,7 +211,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Money possibleNextRepaymentAmount = loan.possibleNextRepaymentAmount();
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
return new LoanTransactionData(null, transactionType, currencyData, earliestUnpaidInstallmentDate,
return new LoanTransactionData(null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
possibleNextRepaymentAmount.getAmount(), null, null, null, null);
}
@@ -237,8 +239,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final MonetaryCurrency monetaryCurrency = new MonetaryCurrency(currency.getCode(), currency.getDecimalPlaces());
final BigDecimal amount = waiveOfInterest.getAmount(monetaryCurrency).getAmount();
return new LoanTransactionData(null, transactionType, currencyData, waiveOfInterest.getTransactionDate(), amount, null, null, null,
null);
return new LoanTransactionData(null, transactionType, null, currencyData, waiveOfInterest.getTransactionDate(), amount, null, null,
null, null);
}
@Override
@@ -247,7 +249,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
context.authenticatedUser();
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF);
return new LoanTransactionData(null, transactionType, null, DateUtils.getLocalDateOfTenant(), null, null, null, null, null);
return new LoanTransactionData(null, transactionType, null, null, DateUtils.getLocalDateOfTenant(), null, null, null, null, null);
}
@Override
@@ -516,19 +518,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
GroupData groupData = null;
if(groupId != null){
if (groupId != null) {
groupData = new GroupData(groupId, groupName, groupExternalId, groupOfficeId, groupStaffId, groupParentId, groupHierarchy);
}
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData,
loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId,
loanPurposeName, loanOfficerId, loanOfficerName, currencyData, principal, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId,
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
interestCalculationPeriodType, expectedFirstRepaymentOnDate, interestChargedFromDate, timeline, loanSummary,
feeChargesDueAtDisbursementCharged);
loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName,
loanOfficerId, loanOfficerName, currencyData, principal, inArrearsTolerance, termFrequency, termPeriodFrequencyType,
numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId, amortizationType,
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged);
}
}
@@ -689,12 +690,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
public String LoanPaymentsSchema() {
return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as `date`, tr.amount as total, "
+ "tr.principal_portion_derived as principal, " + "tr.interest_portion_derived as interest, "
+ "tr.fee_charges_portion_derived as fees, " + "tr.penalty_charges_portion_derived as penalties, "
+ " tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
+ " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, "
+ " pd.payment_type_enum as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "
+ " pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "
+ " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, "
+ " rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode "
+ " from m_loan l " + " join m_loan_transaction tr on tr.loan_id = l.id"
+ " join m_currency rc on rc.`code` = l.currency_code ";
+ " from m_loan l join m_loan_transaction tr on tr.loan_id = l.id"
+ " join m_currency rc on rc.`code` = l.currency_code "
+ " left JOIN m_payment_detail pd ON tr.payment_detail_id = pd.id";
}
@Override
@@ -711,6 +715,22 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Long id = rs.getLong("id");
final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(transactionTypeInt);
PaymentDetailData paymentDetailData = null;
if (transactionType.isPaymentOrReceipt()) {
final Integer paymentTypeId = JdbcSupport.getInteger(rs, "paymentType");
if (paymentTypeId != null) {
EnumOptionData paymentType = LoanEnumerations.paymentType(paymentTypeId);
final String accountNumber = rs.getString("accountNumber");
final String checkNumber = rs.getString("checkNumber");
final String routingCode = rs.getString("routingCode");
final String receiptNumber = rs.getString("receiptNumber");
final String bankNumber = rs.getString("bankNumber");
paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber,
bankNumber);
}
}
final LocalDate date = JdbcSupport.getLocalDate(rs, "date");
final BigDecimal totalAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "total");
final BigDecimal principalPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principal");
@@ -718,8 +738,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees");
final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties");
return new LoanTransactionData(id, transactionType, currencyData, date, totalAmount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion);
return new LoanTransactionData(id, transactionType, paymentDetailData, currencyData, date, totalAmount, principalPortion,
interestPortion, feeChargesPortion, penaltyChargesPortion);
}
}
}
@@ -49,6 +49,8 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.mifosplatform.portfolio.loanaccount.domain.PaymentDetail;
import org.mifosplatform.portfolio.loanaccount.domain.PaymentDetailRepository;
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
@@ -79,6 +81,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final LoanSummaryWrapper loanSummaryWrapper;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final PaymentDetailRepository paymentDetailRepository;
@Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -88,7 +91,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final NoteRepository noteRepository, final ChargeRepositoryWrapper chargeRepository,
final LoanChargeRepository loanChargeRepository, final ApplicationCurrencyRepository applicationCurrencyRepository,
final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanSummaryWrapper loanSummaryWrapper,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) {
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final PaymentDetailRepository paymentDetailRepository) {
this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler;
@@ -102,6 +106,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanUpdateCommandFromApiJsonDeserializer = loanUpdateCommandFromApiJsonDeserializer;
this.loanSummaryWrapper = loanSummaryWrapper;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.paymentDetailRepository = paymentDetailRepository;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -123,8 +128,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = loan.disburse(currentUser, command, currency, existingTransactionIds,
existingReversedTransactionIds);
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
PaymentDetail paymentDetail = PaymentDetail.generatePaymentDetail(command, changes);
if (paymentDetail != null) {
paymentDetailRepository.save(paymentDetail);
}
loan.disburse(currentUser, command, currency, existingTransactionIds, existingReversedTransactionIds, changes, paymentDetail);
if (!changes.isEmpty()) {
this.loanRepository.save(loan);
@@ -153,6 +165,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public Map<String, Object> bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand) {
final AppUser currentUser = context.authenticatedUser();
/****
* TODO Vishwas: Pair with Ashok and re-factor collection sheet
* code-base
*****/
SingleDisbursalCommand[] disbursalCommand = bulkDisbursalCommand.getDisburseTransactions();
Map<String, Object> changes = new LinkedHashMap<String, Object>();
if (disbursalCommand == null) { return changes; }
@@ -167,7 +184,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
changes.putAll(loan.disburse(currentUser, command, currency, existingTransactionIds, existingReversedTransactionIds));
PaymentDetail paymentDetail = PaymentDetail.generatePaymentDetail(command, changes);
if (paymentDetail != null) {
paymentDetailRepository.save(paymentDetail);
}
loan.disburse(currentUser, command, currency, existingTransactionIds, existingReversedTransactionIds, changes, paymentDetail);
if (!changes.isEmpty()) {
this.loanRepository.save(loan);
@@ -190,8 +212,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
context.authenticatedUser();
final Loan loan = retrieveLoanBy(loanId);
final BigDecimal totalInterestCharged = loan.getSummary().getTotalInterestCharged();
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = loan.undoDisbursal(existingTransactionIds, existingReversedTransactionIds);
@@ -207,11 +227,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
// add the previously calculated net charged interest back to
// accountingBridgedate, this would not work if interest
// recalculation in introduced
accountingBridgeData.put("calculatedInterest", totalInterestCharged);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
@@ -248,8 +263,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changes.put("note", noteText);
}
CommandProcessingResultBuilder commandProcessingResultBuilder = saveLoanRepayment(loanId, transactionAmount, transactionDate,
noteText);
PaymentDetail paymentDetail = PaymentDetail.generatePaymentDetail(command, changes);
if (paymentDetail != null) {
paymentDetailRepository.save(paymentDetail);
}
CommandProcessingResultBuilder commandProcessingResultBuilder = saveLoanRepayment(loanId, paymentDetail, transactionAmount,
transactionDate, noteText);
return commandProcessingResultBuilder.withCommandId(command.commandId()) //
.withLoanId(loanId) //
@@ -257,15 +276,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build();
}
private CommandProcessingResultBuilder saveLoanRepayment(final Long loanId, final BigDecimal transactionAmount,
final LocalDate transactionDate, final String noteText) {
private CommandProcessingResultBuilder saveLoanRepayment(final Long loanId, final PaymentDetail paymentDetail,
final BigDecimal transactionAmount, final LocalDate transactionDate, final String noteText) {
final Loan loan = retrieveLoanBy(loanId);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
final LoanTransaction newRepaymentTransaction = LoanTransaction.repayment(repaymentAmount, transactionDate);
final LoanTransaction newRepaymentTransaction = LoanTransaction.repayment(repaymentAmount, paymentDetail, transactionDate);
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds);
@@ -310,7 +329,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (repaymentCommand == null) return changes;
for (SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) {
saveLoanRepayment(singleLoanRepaymentCommand.getLoanId(), singleLoanRepaymentCommand.getTransactionAmount(),
/****
* TODO Vishwas, have a re-look at this implementation, defaulting
* it to null for now
***/
PaymentDetail paymentDetail = null;
saveLoanRepayment(singleLoanRepaymentCommand.getLoanId(), paymentDetail, singleLoanRepaymentCommand.getTransactionAmount(),
bulkRepaymentCommand.getTransactionDate(), bulkRepaymentCommand.getNote());
changes.put("bulkTransations", singleLoanRepaymentCommand);
}
@@ -343,7 +367,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount);
LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmountAsMoney, transactionDate);
PaymentDetail paymentDetail = PaymentDetail.generatePaymentDetail(command, changes);
LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmountAsMoney, paymentDetail, transactionDate);
if (transactionToAdjust.isInterestWaiver()) {
newTransactionDetail = LoanTransaction.waiver(loan, transactionAmountAsMoney, transactionDate);
}
@@ -352,6 +377,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds);
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
if (paymentDetail != null) {
paymentDetailRepository.save(paymentDetail);
}
this.loanTransactionRepository.save(newTransactionDetail);
}
@@ -10,6 +10,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.mifosplatform.portfolio.loanaccount.domain.PaymentType;
import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
@@ -197,6 +198,13 @@ public class LoanEnumerations {
return optionData;
}
public static EnumOptionData paymentType(final Integer id) {
PaymentType paymentType = PaymentType.fromInt(id);
EnumOptionData enumOptionData = new EnumOptionData(paymentType.getValue().longValue(), paymentType.getCode(), paymentType
.getValue().toString());
return enumOptionData;
}
public static LoanTransactionEnumData transactionType(final Integer id) {
return transactionType(LoanTransactionType.fromInt(id));
}
@@ -2,37 +2,39 @@ package org.mifosplatform.portfolio.loanaccount;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
public class LoanTransactionBuilder {
private Money transactionAmount = new MoneyBuilder().build();
private LocalDate transactionDate = LocalDate.now();
private boolean repayment = false;
@SuppressWarnings("unused")
private Money transactionAmount = new MoneyBuilder().build();
@SuppressWarnings("unused")
private LocalDate transactionDate = LocalDate.now();
@SuppressWarnings("unused")
private boolean repayment = false;
public LoanTransaction build() {
LoanTransaction transaction = null;
if (repayment) {
transaction = LoanTransaction.repayment(transactionAmount, transactionDate);
}
return transaction;
}
/** public LoanTransaction build() {
public LoanTransactionBuilder with(final Money newAmount) {
this.transactionAmount = newAmount;
return this;
}
LoanTransaction transaction = null;
public LoanTransactionBuilder with(final LocalDate withTransactionDate) {
this.transactionDate = withTransactionDate;
return this;
}
public LoanTransactionBuilder repayment() {
this.repayment = true;
return this;
}
if (repayment) {
transaction = LoanTransaction.repayment(transactionAmount, transactionDate);
}
return transaction;
}**/
public LoanTransactionBuilder with(final Money newAmount) {
this.transactionAmount = newAmount;
return this;
}
public LoanTransactionBuilder with(final LocalDate withTransactionDate) {
this.transactionDate = withTransactionDate;
return this;
}
public LoanTransactionBuilder repayment() {
this.repayment = true;
return this;
}
}