support repayment strategy approach for deciding how loan transactions should be processed against the loan schedule.

This commit is contained in:
Keith Woodlock
2012-07-30 16:59:55 +01:00
parent 4dc10ae648
commit 68ea71a68f
8 changed files with 89 additions and 33 deletions
@@ -18,6 +18,11 @@ ADD COLUMN `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
ADD KEY `FK_ltp_strategy` (`loan_transaction_strategy_id`),
ADD CONSTRAINT `FK_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`);
ALTER TABLE `portfolio_loan`
ADD COLUMN `loan_transaction_strategy_id` bigint(20) DEFAULT NULL,
ADD KEY `FK_loan_ltp_strategy` (`loan_transaction_strategy_id`),
ADD CONSTRAINT `FK_loan_ltp_strategy` FOREIGN KEY (`loan_transaction_strategy_id`) REFERENCES `ref_loan_transaction_processing_strategy` (`id`);
-- update ref table with out-of-the-box strategies
INSERT INTO `ref_loan_transaction_processing_strategy`
@@ -15,6 +15,7 @@ public class SubmitLoanApplicationCommand {
private final String externalId;
private final Long fundId;
private final Long transactionProcessingStrategyId;
private final BigDecimal principal;
private final BigDecimal inArrearsTolerance;
@@ -39,7 +40,8 @@ public class SubmitLoanApplicationCommand {
private LoanSchedule loanSchedule;
public SubmitLoanApplicationCommand(
final Long clientId, final Long productId, final String externalId, final Long fundId,
final Long clientId, final Long productId, final String externalId,
final Long fundId, final Long transactionProcessingStrategyId,
final LocalDate submittedOnDate, final String submittedOnNote,
final LocalDate expectedDisbursementDate,
final LocalDate repaymentsStartingFromDate,
@@ -55,6 +57,7 @@ public class SubmitLoanApplicationCommand {
this.productId = productId;
this.externalId = externalId;
this.fundId = fundId;
this.transactionProcessingStrategyId = transactionProcessingStrategyId;
this.submittedOnDate = submittedOnDate;
this.submittedOnNote = submittedOnNote;
@@ -104,6 +107,10 @@ public class SubmitLoanApplicationCommand {
public Long getFundId() {
return fundId;
}
public Long getTransactionProcessingStrategyId() {
return transactionProcessingStrategyId;
}
public BigDecimal getPrincipal() {
return principal;
@@ -872,7 +872,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
Map<String, String> requestMap = gsonConverter.fromJson(json, typeOfMap);
Set<String> supportedParams = new HashSet<String>(
Arrays.asList("clientId", "productId", "externalId", "fundId",
Arrays.asList("clientId", "productId", "externalId", "fundId", "transactionProcessingStrategyId",
"principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments",
"repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType",
"expectedDisbursementDate", "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote",
@@ -886,6 +886,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
Long clientId = extractLongParameter("clientId", requestMap, modifiedParameters);
Long productId = extractLongParameter("productId", requestMap, modifiedParameters);
Long fundId = extractLongParameter("fundId", requestMap, modifiedParameters);
Long transactionProcessingStrategyId = extractLongParameter("transactionProcessingStrategyId", requestMap, modifiedParameters);
String externalId = extractStringParameter("externalId", requestMap, modifiedParameters);
BigDecimal principal = extractBigDecimalParameter("principal", requestMap, modifiedParameters);
@@ -908,7 +909,8 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
String submittedOnNote = extractStringParameter("submittedOnNote", requestMap, modifiedParameters);
return new SubmitLoanApplicationCommand(clientId, productId, externalId, fundId, submittedOnDate, submittedOnNote,
return new SubmitLoanApplicationCommand(clientId, productId, externalId, fundId, transactionProcessingStrategyId,
submittedOnDate, submittedOnNote,
expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate,
principal, interestRatePerPeriod, interestRateFrequencyTypeValue, interestTypeValue, interestCalculationPeriodTypeValue,
repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationTypeValue, inArrearsToleranceValue);
@@ -50,6 +50,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@ManyToOne
@JoinColumn(name = "fund_id", nullable = true)
private Fund fund;
@ManyToOne
@JoinColumn(name = "loan_transaction_strategy_id", nullable = true)
private LoanTransactionProcessingStrategy transactionProcessingStrategy;
@Column(name = "external_id")
private String externalId;
@@ -136,11 +140,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Transient
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessor = new LoanRepaymentScheduleTransactionProcessorFactory();
public static Loan createNew(final Fund fund,
public static Loan createNew(final Fund fund, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
final LoanProduct loanProduct, final Client client,
final LoanProductRelatedDetail loanRepaymentScheduleDetail) {
return new Loan(client, fund, loanProduct, loanRepaymentScheduleDetail,
null);
return new Loan(client, fund, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, null);
}
public Loan() {
@@ -149,15 +152,12 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.loanRepaymentScheduleDetail = null;
}
public Loan(final Client client, Fund fund, final LoanProduct loanProduct,
public Loan(final Client client, Fund fund, LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct,
final LoanProductRelatedDetail loanRepaymentScheduleDetail,
final LoanStatus loanStatus) {
this.client = client;
if (fund != null) {
this.fund = fund;
} else {
this.fund = loanProduct.getFund();
}
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.loanProduct = loanProduct;
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.loanStatus = loanStatus;
@@ -445,7 +445,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
Collections.sort(repaymentsOrWaivers, transactionComparator);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor();
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
if (isTransactionChronologicallyLatest && !adjusted) {
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments);
} else {
@@ -1,21 +1,32 @@
package org.mifosng.platform.loan.domain;
import org.mifosng.platform.infrastructure.MifosPlatformTenant;
import org.mifosng.platform.infrastructure.ThreadLocalContextUtil;
/**
*
*/
public class LoanRepaymentScheduleTransactionProcessorFactory {
public LoanRepaymentScheduleTransactionProcessor determineProcessor() {
public LoanRepaymentScheduleTransactionProcessor determineProcessor(final LoanTransactionProcessingStrategy transactionProcessingStrategy) {
LoanRepaymentScheduleTransactionProcessor processor = new HeavensFamilyLoanRepaymentScheduleTransactionProcessor();
// FIXME - Just temp way of deciding what 'repayment strategy' to use for now.
MifosPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
if ("mifostenant-creocore".equalsIgnoreCase(tenant.getSchemaName())) {
processor = new CreocoreLoanRepaymentScheduleTransactionProcessor();
if (transactionProcessingStrategy != null) {
if (transactionProcessingStrategy.isStandardMifosStrategy()) {
processor = new HeavensFamilyLoanRepaymentScheduleTransactionProcessor();
}
if (transactionProcessingStrategy.isHeavensfamilyStrategy()) {
processor = new HeavensFamilyLoanRepaymentScheduleTransactionProcessor();
}
if (transactionProcessingStrategy.isCreocoreStrategy()) {
processor = new CreocoreLoanRepaymentScheduleTransactionProcessor();
}
if (transactionProcessingStrategy.isIndianRBIStrategy()) {
processor = new AdhikarLoanRepaymentScheduleTransactionProcessor();
}
}
return processor;
@@ -25,11 +25,23 @@ public class LoanTransactionProcessingStrategy extends AbstractAuditableCustom<A
//
}
public String code() {
return this.code;
}
public TransactionProcessingStrategyData toData() {
return new TransactionProcessingStrategyData(this.getId(), this.code, this.name);
}
public boolean isStandardMifosStrategy() {
return "mifos-standard-strategy".equalsIgnoreCase(this.code);
}
public boolean isHeavensfamilyStrategy() {
return "heavensfamily-strategy".equalsIgnoreCase(this.code);
}
public boolean isCreocoreStrategy() {
return "creocore-strategy".equalsIgnoreCase(this.code);
}
public boolean isIndianRBIStrategy() {
return "rbi-india-strategy".equalsIgnoreCase(this.code);
}
}
@@ -13,6 +13,7 @@ import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.exceptions.ClientNotFoundException;
import org.mifosng.platform.exceptions.FundNotFoundException;
import org.mifosng.platform.exceptions.LoanProductNotFoundException;
import org.mifosng.platform.exceptions.LoanTransactionProcessingStrategyNotFoundException;
import org.mifosng.platform.fund.domain.Fund;
import org.mifosng.platform.fund.domain.FundRepository;
import org.mifosng.platform.loan.domain.AmortizationMethod;
@@ -27,6 +28,8 @@ import org.mifosng.platform.loan.domain.LoanProductRepository;
import org.mifosng.platform.loan.domain.LoanRepaymentScheduleInstallment;
import org.mifosng.platform.loan.domain.LoanStatus;
import org.mifosng.platform.loan.domain.LoanStatusRepository;
import org.mifosng.platform.loan.domain.LoanTransactionProcessingStrategy;
import org.mifosng.platform.loan.domain.LoanTransactionProcessingStrategyRepository;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.loanschedule.domain.AprCalculator;
import org.springframework.beans.factory.annotation.Autowired;
@@ -40,18 +43,22 @@ public class LoanAssembler {
private final ClientRepository clientRepository;
private final AprCalculator aprCalculator = new AprCalculator();
private final FundRepository fundRepository;
private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
@Autowired
public LoanAssembler(final LoanStatusRepository loanStatusRepository,
final LoanProductRepository loanProductRepository,
final ClientRepository clientRepository, final FundRepository fundRepository) {
final ClientRepository clientRepository,
final FundRepository fundRepository,
final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository) {
this.loanStatusRepository = loanStatusRepository;
this.loanProductRepository = loanProductRepository;
this.clientRepository = clientRepository;
this.fundRepository = fundRepository;
this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
}
public Loan assembleFrom(SubmitLoanApplicationCommand command) {
public Loan assembleFrom(final SubmitLoanApplicationCommand command) {
LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId());
if (loanProduct == null) {
@@ -87,8 +94,9 @@ public class LoanAssembler {
// associating fund with loan product at creation is optional for now.
Fund fund = findFundByIdIfProvided(command.getFundId());
Loan loan = Loan.createNew(fund, loanProduct, client, loanRepaymentScheduleDetail);
LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
Loan loan = Loan.createNew(fund, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail);
loan.setExternalId(command.getExternalId());
for (ScheduledLoanInstallment scheduledLoanInstallment : loanRepaymentSchedule) {
@@ -125,4 +133,15 @@ public class LoanAssembler {
}
return fund;
}
}
private LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) {
LoanTransactionProcessingStrategy strategy = null;
if (transactionProcessingStrategyId != null) {
strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId);
if (strategy == null) {
throw new LoanTransactionProcessingStrategyNotFoundException(transactionProcessingStrategyId);
}
}
return strategy;
}
}
@@ -85,14 +85,14 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
}
private LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) {
LoanTransactionProcessingStrategy fund = null;
LoanTransactionProcessingStrategy strategy = null;
if (transactionProcessingStrategyId != null) {
fund = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId);
if (fund == null) {
strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId);
if (strategy == null) {
throw new LoanTransactionProcessingStrategyNotFoundException(transactionProcessingStrategyId);
}
}
return fund;
return strategy;
}
private Fund findFundByIdIfProvided(final Long fundId) {