added borrower cycle to loan products

This commit is contained in:
Pramod Nuthakki
2013-11-24 12:35:50 +05:30
parent 35652b1abd
commit a7632d4fae
21 changed files with 1260 additions and 50 deletions
@@ -0,0 +1,19 @@
CREATE TABLE `m_product_loan_variations_borrower_cycle` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_product_id` BIGINT(20) NOT NULL DEFAULT '0',
`borrower_cycle_number` INT(3) NOT NULL DEFAULT '0',
`value_condition` INT(1) NOT NULL DEFAULT '0',
`param_type` INT(1) NOT NULL DEFAULT '0',
`default_value` DECIMAL(19,6) NOT NULL DEFAULT '0.000000',
`max_value` DECIMAL(19,6) NULL DEFAULT NULL,
`min_value` DECIMAL(19,6) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `borrower_cycle_loan_product_FK` (`loan_product_id`),
CONSTRAINT `borrower_cycle_loan_product_FK` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
ALTER TABLE `m_product_loan`
ADD COLUMN `use_borrower_cycle` TINYINT(1) NOT NULL DEFAULT '0' AFTER `include_in_borrower_cycle`;
@@ -364,6 +364,43 @@ public class DataValidatorBuilder {
}
return this;
}
public DataValidatorBuilder integerGreaterThanNumber(Integer number) {
if (this.value == null && this.ignoreNullValue) { return this; }
if (this.value != null) {
final Integer intValue = Integer.valueOf(this.value.toString());
if (intValue < number+1) {
final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
.append(this.parameter).append(".not.greater.than.specified.number");
final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
" must be greater than ").append(number);
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
defaultEnglishMessage.toString(), this.parameter, intValue, number);
this.dataValidationErrors.add(error);
}
}
return this;
}
public DataValidatorBuilder integerSameAsNumber(Integer number) {
if (this.value == null && this.ignoreNullValue) { return this; }
if (this.value != null) {
final Integer intValue = Integer.valueOf(this.value.toString());
if (intValue != number) {
final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
.append(this.parameter).append(".not.equal.to.specified.number");
final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(
" must be same as").append(number);
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
defaultEnglishMessage.toString(), this.parameter, intValue, number);
this.dataValidationErrors.add(error);
}
}
return this;
}
/*
* should be used with .notNull() before it
@@ -190,7 +190,7 @@ public class LoansApiResource {
Long officeId = null;
if (productId != null) {
newLoanAccount = this.loanReadPlatformService.retrieveLoanProductDetailsTemplate(productId);
newLoanAccount = this.loanReadPlatformService.retrieveLoanProductDetailsTemplate(productId, clientId, groupId);
}
if (templateType == null) {
@@ -29,8 +29,10 @@ import org.mifosplatform.portfolio.fund.data.FundData;
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
import org.mifosplatform.portfolio.loanaccount.guarantor.data.GuarantorData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.mifosplatform.portfolio.note.data.NoteData;
import org.springframework.util.CollectionUtils;
@@ -449,7 +451,8 @@ public class LoanAccountData {
final Collection<EnumOptionData> interestRateFrequencyTypeOptions, final Collection<EnumOptionData> amortizationTypeOptions,
final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions,
final Collection<CodeValueData> loanPurposeOptions, final Collection<CodeValueData> loanCollateralOptions) {
final Collection<CodeValueData> loanPurposeOptions, final Collection<CodeValueData> loanCollateralOptions,
final Integer loanCycleNumber) {
final Long id = null;
final String accountNo = null;
@@ -468,16 +471,16 @@ public class LoanAccountData {
final Long loanOfficerId = null;
final String loanOfficerName = null;
final CurrencyData currencyData = null;
final BigDecimal principal = null;
final BigDecimal totalOverpaid = null;
final BigDecimal inArrearsTolerance = null;
final Integer numberOfRepayments = null;
final Integer repaymentEvery = null;
final EnumOptionData repaymentFrequencyType = null;
final Long transactionProcessingStrategyId = null;
final String transactionProcessingStrategyName = null;
final EnumOptionData amortizationType = null;
final BigDecimal interestRatePerPeriod = null;
final EnumOptionData interestRateFrequencyType = null;
final BigDecimal annualInterestRate = null;
final EnumOptionData interestType = null;
@@ -500,7 +503,6 @@ public class LoanAccountData {
final Collection<CalendarData> calendarOptions = null;
final Collection<StaffData> loanOfficerOptions = null;
final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
final Collection<LoanChargeData> charges = new ArrayList<LoanChargeData>();
@@ -512,21 +514,50 @@ public class LoanAccountData {
final Integer loanProductCounter = null;
final PortfolioAccountData linkedAccount = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
BigDecimal principal = null;
BigDecimal interestRatePerPeriod = null;
Integer numberOfRepayments = null;
if (product.useBorrowerCycle() && loanCycleNumber > 0) {
Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle = product
.getPrincipalVariationsForBorrowerCycle();
Collection<LoanProductBorrowerCycleVariationData> interestForVariationsForBorrowerCycle = product
.getInterestRateVariationsForBorrowerCycle();
Collection<LoanProductBorrowerCycleVariationData> repaymentVariationsForBorrowerCycle = product
.getNumberOfRepaymentVariationsForBorrowerCycle();
principal = fetchLoanCycleDefaultValue(principalVariationsForBorrowerCycle, loanCycleNumber);
interestRatePerPeriod = fetchLoanCycleDefaultValue(interestForVariationsForBorrowerCycle, loanCycleNumber);
BigDecimal numberofRepaymentval = fetchLoanCycleDefaultValue(repaymentVariationsForBorrowerCycle, loanCycleNumber);
if (numberofRepaymentval != null) {
numberOfRepayments = numberofRepaymentval.intValue();
}
}
if (principal == null) {
principal = product.getPrincipal();
}
if (interestRatePerPeriod == null) {
interestRatePerPeriod = product.getInterestRatePerPeriod();
}
if (numberOfRepayments == null) {
numberOfRepayments = product.getNumberOfRepayments();
}
final Integer termFrequency = numberOfRepayments * product.getRepaymentEvery();
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType,
product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId,
loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(), product.getPrincipal(), totalOverpaid,
product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(),
product.getRepaymentEvery(), product.getRepaymentFrequencyType(), product.getTransactionProcessingStrategyId(),
transactionProcessingStrategyName, product.getAmortizationType(), product.getInterestRatePerPeriod(),
product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(),
product.getInterestCalculationPeriodType(), expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(),
product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary,
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount);
loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(), principal, totalOverpaid,
product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, numberOfRepayments, product.getRepaymentEvery(),
product.getRepaymentFrequencyType(), product.getTransactionProcessingStrategyId(), transactionProcessingStrategyName,
product.getAmortizationType(), interestRatePerPeriod, product.getInterestRateFrequencyType(),
product.getAnnualInterestRate(), product.getInterestType(), product.getInterestCalculationPeriodType(),
expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getGraceOnInterestPayment(),
product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged,
repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -895,4 +926,31 @@ public class LoanAccountData {
public Long clientId() {
return this.clientId;
}
private static BigDecimal fetchLoanCycleDefaultValue(Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationData,
Integer loanCycleNumber) {
BigDecimal defaultValue = null;
Integer cycleNumberSelected = 0;
for (LoanProductBorrowerCycleVariationData data : borrowerCycleVariationData) {
if (isLoanCycleValuesWhenConditionEqual(loanCycleNumber, data)
|| isLoanCycleValuesWhenConditionGreterthan(loanCycleNumber, cycleNumberSelected, data)) {
cycleNumberSelected = data.getBorrowerCycleNumber();
defaultValue = data.getDefaultValue();
}
}
return defaultValue;
}
private static boolean isLoanCycleValuesWhenConditionGreterthan(Integer loanCycleNumber, Integer cycleNumberSelected,
LoanProductBorrowerCycleVariationData data) {
return data.getBorrowerCycleNumber() < loanCycleNumber
&& data.getValueConditionType().equals(LoanProductValueConditionType.GRETERTHAN)
&& cycleNumberSelected < data.getBorrowerCycleNumber();
}
private static boolean isLoanCycleValuesWhenConditionEqual(Integer loanCycleNumber, LoanProductBorrowerCycleVariationData data) {
return data.getBorrowerCycleNumber() == loanCycleNumber && data.getValueConditionType().equals(LoanProductValueConditionType.EQUAL);
}
}
@@ -27,6 +27,7 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.account.domain.AccountAssociations;
import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
@@ -112,6 +113,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository;
private final LoanChargeRepository loanChargeRepository;
private final LoanReadPlatformService loanReadPlatformService;
@Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -126,7 +128,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository,
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository,
final LoanChargeRepository loanChargeRepository) {
final LoanChargeRepository loanChargeRepository, final LoanReadPlatformService loanReadPlatformService) {
this.context = context;
this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -150,6 +152,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository;
this.loanChargeRepository = loanChargeRepository;
this.loanReadPlatformService = loanReadPlatformService;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -172,9 +175,21 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final Long productId = this.fromJsonHelper.extractLongNamed("productId", command.parsedJson());
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, loanProduct);
if (loanProduct.useBorrowerCycle()) {
final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", command.parsedJson());
final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", command.parsedJson());
Integer cycleNumber = 0;
if (clientId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId);
} else if (groupId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue());
}
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
loanProduct, cycleNumber);
} else {
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
loanProduct);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
final Loan newLoanApplication = this.loanAssembler.assembleFrom(command, currentUser);
@@ -292,6 +307,24 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
if (!changes.containsKey("interestRateFrequencyType")) {
existingLoanApplication.updateInterestRateFrequencyType();
}
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
if (loanProduct.useBorrowerCycle()) {
final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", command.parsedJson());
final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", command.parsedJson());
Integer cycleNumber = 0;
if (clientId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId);
} else if (groupId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue());
}
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
loanProduct, cycleNumber);
} else {
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
loanProduct);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
validateSubmittedOnDate(existingLoanApplication);
@@ -362,7 +395,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
for (final LoanCharge loanCharge : existingLoanApplication.charges()) {
if (loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()) {
final Set<LoanInstallmentCharge> chargePerInstallments = existingLoanApplication.generateInstallmentLoanCharges(loanCharge);
final Set<LoanInstallmentCharge> chargePerInstallments = existingLoanApplication
.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
@@ -41,7 +41,7 @@ public interface LoanReadPlatformService {
LoanAccountData retrieveTemplateWithCompleteGroupAndProductDetails(Long groupId, Long productId);
LoanAccountData retrieveLoanProductDetailsTemplate(Long productId);
LoanAccountData retrieveLoanProductDetailsTemplate(Long productId, Long clientId, Long groupId);
LoanAccountData retrieveClientDetailsTemplate(Long clientId);
@@ -61,4 +61,8 @@ public interface LoanReadPlatformService {
* The 'overdue-charge' is only ever applied once to an installment and as a result overdue installments with this charge already applied are not returned.
*/
Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallmentsNotAlreadyPenalized();
Integer retriveLoanCounter(Long groupId, Integer loanType);
Integer retriveLoanCounter(Long clientId);
}
@@ -32,6 +32,7 @@ import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.organisation.staff.service.StaffReadPlatformService;
import org.mifosplatform.portfolio.account.data.AccountTransferData;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.accountdetails.service.AccountEnumerations;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
@@ -1039,7 +1040,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public LoanAccountData retrieveLoanProductDetailsTemplate(final Long productId) {
public LoanAccountData retrieveLoanProductDetailsTemplate(final Long productId, Long clientId, Long groupId) {
this.context.authenticatedUser();
@@ -1063,10 +1064,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
.retrieveCodeValuesByCode("LoanCollateral");
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
Integer loanCycleCounter = null;
if(loanProduct.useBorrowerCycle()){
if(clientId == null){
loanCycleCounter = retriveLoanCounter(groupId,AccountType.GROUP.getValue());
}else{
loanCycleCounter = retriveLoanCounter(clientId);
}
}
return LoanAccountData.loanProductWithTemplateDefaults(loanProduct, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions);
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions, loanCycleCounter);
}
@Override
@@ -1150,4 +1158,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " and plc.charge_id not in (SELECT charge_id from m_loan_charge as mlc where mlc.loan_id = ml.id and mlc.charge_id = plc.charge_id and mlc.due_for_collection_as_of_date > fromdate AND mlc.due_for_collection_as_of_date <= duedate) ";
return this.jdbcTemplate.query(sql, rm, new Object[] {});
}
@Override
public Integer retriveLoanCounter(Long groupId,Integer loanType){
final String sql = "Select MAX(l.loan_counter) from m_loan l where l.group_id = ? "
+ " and l.loan_type_enum = ?";
return this.jdbcTemplate.queryForInt(sql,groupId,loanType);
}
@Override
public Integer retriveLoanCounter(Long clientId){
final String sql = "Select MAX(l.loan_counter) from m_loan l where l.client_id = ?";
return this.jdbcTemplate.queryForInt(sql,clientId);
}
}
@@ -0,0 +1,51 @@
package org.mifosplatform.portfolio.loanproduct;
public interface LoanProductConstants {
public static final String useBorrowerCycleParameterName = "useBorrowerCycle";
public static final String principalVariationsForBorrowerCycleParameterName = "principalVariationsForBorrowerCycle";
public static final String interestRateVariationsForBorrowerCycleParameterName = "interestRateVariationsForBorrowerCycle";
public static final String numberOfRepaymentVariationsForBorrowerCycleParameterName = "numberOfRepaymentVariationsForBorrowerCycle";
public static final String defaultValueParameterName = "defaultValue";
public static final String minValueParameterName = "minValue";
public static final String maxValueParameterName = "maxValue";
public static final String valueConditionTypeParamName = "valueConditionType";
public static final String borrowerCycleNumberParamName = "borrowerCycleNumber";
public static final String borrowerCycleIdParameterName = "id";
public static final String principalPerCycleParameterName = "principalPerCycle";
public static final String minPrincipalPerCycleParameterName = "minPrincipalPerCycle";
public static final String maxPrincipalPerCycleParameterName = "maxPrincipalPerCycle";
public static final String principalValueUsageConditionParamName = "principalValueUsageCondition";
public static final String principalCycleNumbersParamName = "principalCycleNumbers";
public static final String numberOfRepaymentsPerCycleParameterName = "numberOfRepaymentsPerCycle";
public static final String minNumberOfRepaymentsPerCycleParameterName = "minNumberOfRepaymentsPerCycle";
public static final String maxNumberOfRepaymentsPerCycleParameterName = "maxNumberOfRepaymentsPerCycle";
public static final String repaymentValueUsageConditionParamName = "repaymentValueUsageCondition";
public static final String repaymentCycleNumberParamName = "repaymentCycleNumber";
public static final String interestRatePerPeriodPerCycleParameterName = "interestRatePerPeriodPerCycle";
public static final String minInterestRatePerPeriodPerCycleParameterName = "minInterestRatePerPeriodPerCycle";
public static final String maxInterestRatePerPeriodPerCycleParameterName = "maxInterestRatePerPeriodPerCycle";
public static final String interestRateValueUsageConditionParamName = "interestRateValueUsageCondition";
public static final String interestRateCycleNumberParamName = "interestRateCycleNumber";
public static final String principal = "principal";
public static final String minPrincipal = "minPrincipal";
public static final String maxPrincipal = "maxPrincipalValue";
public static final String interestRatePerPeriod = "interestRatePerPeriod";
public static final String minInterestRatePerPeriod = "minInterestRatePerPeriod";
public static final String maxInterestRatePerPeriod = "maxInterestRatePerPeriod";
public static final String numberOfRepayments = "numberOfRepayments";
public static final String minNumberOfRepayments = "minNumberOfRepayments";
public static final String maxNumberOfRepayments = "maxNumberOfRepayments";
public static final String VALUE_CONDITION_TYPE_ERROR = "condition.type.must.end.with.greterthan";
}
@@ -264,11 +264,13 @@ public class LoanProductsApiResource {
final List<EnumOptionData> accountingRuleTypeOptions = this.accountingDropdownReadPlatformService
.retrieveAccountingRuleTypeOptions();
final List<EnumOptionData> loanCycleValueConditionTypeOptions = this.dropdownReadPlatformService.retrieveLoanCycleValueConditionTypeOptions();
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions,
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions,
interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions,
accountingRuleTypeOptions);
accountingRuleTypeOptions,loanCycleValueConditionTypeOptions);
}
}
@@ -0,0 +1,54 @@
package org.mifosplatform.portfolio.loanproduct.data;
import java.math.BigDecimal;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
public class LoanProductBorrowerCycleVariationData {
@SuppressWarnings("unused")
private final Long id;
private final Integer borrowerCycleNumber;
private final EnumOptionData paramType;
private final EnumOptionData valueConditionType;
@SuppressWarnings("unused")
private final BigDecimal minValue;
@SuppressWarnings("unused")
private final BigDecimal maxValue;
private final BigDecimal defaultValue;
public LoanProductBorrowerCycleVariationData(final Long id,final Integer borrowerCycleNumber,final EnumOptionData paramType,final EnumOptionData valueConditionType,final BigDecimal defaultValue,final BigDecimal minValue,
final BigDecimal maxValue) {
this.id = id;
this.borrowerCycleNumber = borrowerCycleNumber;
this.paramType = paramType;
this.valueConditionType = valueConditionType;;
this.minValue = minValue;
this.maxValue = maxValue;
this.defaultValue = defaultValue;
}
public LoanProductParamType getParamType(){
return LoanProductParamType.fromInt(this.paramType.getId().intValue());
}
public Integer getBorrowerCycleNumber() {
return this.borrowerCycleNumber;
}
public LoanProductValueConditionType getValueConditionType() {
return LoanProductValueConditionType.fromInt(this.valueConditionType.getId().intValue());
}
public BigDecimal getDefaultValue() {
return this.defaultValue;
}
}
@@ -39,6 +39,7 @@ public class LoanProductData {
private final Long fundId;
private final String fundName;
private final boolean includeInBorrowerCycle;
private final boolean useBorrowerCycle;
private final LocalDate startDate;
private final LocalDate closeDate;
private final String status;
@@ -72,7 +73,10 @@ public class LoanProductData {
// charges
private final Collection<ChargeData> charges;
private final Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle;
private final Collection<LoanProductBorrowerCycleVariationData> interestRateVariationsForBorrowerCycle;
private final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariationsForBorrowerCycle;
// accounting
private final EnumOptionData accountingRule;
private Map<String, Object> accountingMappings;
@@ -99,7 +103,8 @@ public class LoanProductData {
private final List<EnumOptionData> accountingRuleOptions;
@SuppressWarnings("unused")
private final Map<String, List<GLAccountData>> accountingMappingOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> valueConditionTypeOptions;
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -131,8 +136,12 @@ public class LoanProductData {
final Integer graceOnInterestPayment = null;
final Integer graceOnInterestCharged = null;
final Collection<ChargeData> charges = null;
final Collection<LoanProductBorrowerCycleVariationData> principalVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Collection<LoanProductBorrowerCycleVariationData> interestRateVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final EnumOptionData accountingType = null;
final boolean includeInBorrowerCycle = false;
final boolean useBorrowerCycle = false;
final LocalDate startDate = null;
final LocalDate closeDate = null;
final String status = null;
@@ -142,7 +151,8 @@ public class LoanProductData {
maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
accountingType, includeInBorrowerCycle, startDate, closeDate, status, externalId);
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations);
}
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
@@ -175,17 +185,23 @@ public class LoanProductData {
final Collection<ChargeData> charges = null;
final EnumOptionData accountingType = null;
final boolean includeInBorrowerCycle = false;
final boolean useBorrowerCycle = false;
final LocalDate startDate = null;
final LocalDate closeDate = null;
final String status = null;
final String externalId = null;
final Collection<LoanProductBorrowerCycleVariationData> principalVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Collection<LoanProductBorrowerCycleVariationData> interestRateVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments,
minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod,
maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
accountingType, includeInBorrowerCycle, startDate, closeDate, status, externalId);
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations);
}
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
@@ -222,8 +238,13 @@ public class LoanProductData {
final Integer graceOnInterestCharged = null;
final Collection<ChargeData> charges = null;
final Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Collection<LoanProductBorrowerCycleVariationData> interestRateVariationsForBorrowerCycle = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final EnumOptionData accountingType = AccountingEnumerations.accountingRuleType(AccountingRuleType.NONE);
final boolean includeInBorrowerCycle = false;
final boolean useBorrowerCycle = false;
final LocalDate startDate = null;
final LocalDate closeDate = null;
final String status = null;
@@ -234,7 +255,8 @@ public class LoanProductData {
maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
accountingType, includeInBorrowerCycle, startDate, closeDate, status, externalId);
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -258,7 +280,9 @@ public class LoanProductData {
final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName,
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
final Collection<ChargeData> charges, final EnumOptionData accountingType, final boolean includeInBorrowerCycle,
final LocalDate startDate, final LocalDate closeDate, final String status, final String externalId) {
boolean useBorrowerCycle, final LocalDate startDate, final LocalDate closeDate, final String status, final String externalId,
Collection<LoanProductBorrowerCycleVariationData> principalVariations, Collection<LoanProductBorrowerCycleVariationData> interestRateVariations,
Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations) {
this.id = id;
this.name = name;
this.description = description;
@@ -290,6 +314,7 @@ public class LoanProductData {
this.charges = charges;
this.accountingRule = accountingType;
this.includeInBorrowerCycle = includeInBorrowerCycle;
this.useBorrowerCycle = useBorrowerCycle;
this.startDate = startDate;
this.closeDate = closeDate;
this.status = status;
@@ -313,6 +338,10 @@ public class LoanProductData {
this.paymentChannelToFundSourceMappings = null;
this.feeToIncomeAccountMappings = null;
this.penaltyToIncomeAccountMappings = null;
this.valueConditionTypeOptions = null;
this.principalVariationsForBorrowerCycle = principalVariations;
this.interestRateVariationsForBorrowerCycle = interestRateVariations;
this.numberOfRepaymentVariationsForBorrowerCycle = numberOfRepaymentVariations;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -321,7 +350,8 @@ public class LoanProductData {
final List<EnumOptionData> interestTypeOptions, final List<EnumOptionData> interestCalculationPeriodTypeOptions,
final List<EnumOptionData> repaymentFrequencyTypeOptions, final List<EnumOptionData> interestRateFrequencyTypeOptions,
final Collection<FundData> fundOptions, final Collection<TransactionProcessingStrategyData> transactionStrategyOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final List<EnumOptionData> accountingRuleOptions) {
final Map<String, List<GLAccountData>> accountingMappingOptions, final List<EnumOptionData> accountingRuleOptions,
final List<EnumOptionData> valueConditionTypeOptions) {
this.id = productData.id;
this.name = productData.name;
this.description = productData.description;
@@ -351,6 +381,9 @@ public class LoanProductData {
this.externalId = productData.externalId;
this.charges = nullIfEmpty(productData.charges());
this.principalVariationsForBorrowerCycle = productData.principalVariationsForBorrowerCycle;
this.interestRateVariationsForBorrowerCycle = productData.interestRateVariationsForBorrowerCycle;
this.numberOfRepaymentVariationsForBorrowerCycle = productData.numberOfRepaymentVariationsForBorrowerCycle;
this.accountingRule = productData.accountingRule;
this.accountingMappings = productData.accountingMappings;
this.paymentChannelToFundSourceMappings = productData.paymentChannelToFundSourceMappings;
@@ -379,6 +412,7 @@ public class LoanProductData {
this.graceOnInterestPayment = productData.graceOnInterestPayment;
this.graceOnInterestCharged = productData.graceOnInterestCharged;
this.includeInBorrowerCycle = productData.includeInBorrowerCycle;
this.useBorrowerCycle = productData.useBorrowerCycle;
this.amortizationTypeOptions = amortizationTypeOptions;
this.interestTypeOptions = interestTypeOptions;
@@ -388,6 +422,7 @@ public class LoanProductData {
this.accountingMappingOptions = accountingMappingOptions;
this.accountingRuleOptions = accountingRuleOptions;
this.valueConditionTypeOptions = valueConditionTypeOptions;
}
@@ -549,4 +584,24 @@ public class LoanProductData {
public int hashCode() {
return this.id.hashCode();
}
public boolean useBorrowerCycle() {
return this.useBorrowerCycle;
}
public Collection<LoanProductBorrowerCycleVariationData> getPrincipalVariationsForBorrowerCycle() {
return this.principalVariationsForBorrowerCycle;
}
public Collection<LoanProductBorrowerCycleVariationData> getInterestRateVariationsForBorrowerCycle() {
return this.interestRateVariationsForBorrowerCycle;
}
public Collection<LoanProductBorrowerCycleVariationData> getNumberOfRepaymentVariationsForBorrowerCycle() {
return this.numberOfRepaymentVariationsForBorrowerCycle;
}
}
@@ -6,12 +6,16 @@
package org.mifosplatform.portfolio.loanproduct.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
@@ -19,12 +23,15 @@ import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate;
import org.mifosplatform.accounting.common.AccountingRuleType;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
@@ -33,17 +40,19 @@ import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.springframework.data.jpa.domain.AbstractPersistable;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
* Loan products allow for categorisation of an organisations loans into
* something meaningful to them.
*
*
* They provide a means of simplifying creation/maintenance of loans. They can
* also allow for product comparison to take place when reporting.
*
*
* They allow for constraints to be added at product level.
*/
@Entity
@@ -81,6 +90,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
@Column(name = "include_in_borrower_cycle")
private boolean includeInBorrowerCycle;
@Column(name = "use_borrower_cycle")
private boolean useBorrowerCycle;
@Column(name = "start_date", nullable = true)
@Temporal(TemporalType.DATE)
private Date startDate;
@@ -92,6 +104,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
@Column(name = "external_id", length = 100, nullable = true, unique = true)
private String externalId;
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loanProduct", orphanRemoval = true)
private Set<LoanProductBorrowerCycleVariations> borrowerCycleVariations = new HashSet<LoanProductBorrowerCycleVariations>();
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
@@ -137,12 +153,186 @@ public class LoanProduct extends AbstractPersistable<Long> {
final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
final String externalId = command.stringValueOfParameterNamedAllowingNull("externalId");
final boolean useBorrowerCycle = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.useBorrowerCycleParameterName);
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations = new HashSet<LoanProductBorrowerCycleVariations>();
if (useBorrowerCycle) {
populateBorrowerCyclevariations(command, loanProductBorrowerCycleVariations);
}
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, minPrincipal, maxPrincipal,
interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate,
interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, numberOfRepayments,
minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate,
externalId);
externalId, useBorrowerCycle, loanProductBorrowerCycleVariations);
}
/**
* @param command
* @param loanProductBorrowerCycleVariations
*/
private static void populateBorrowerCyclevariations(final JsonCommand command,
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
assemblePrincipalVariations(command, loanProductBorrowerCycleVariations);
assembleRepaymentVariations(command, loanProductBorrowerCycleVariations);
assembleInterestRateVariations(command, loanProductBorrowerCycleVariations);
}
/**
* @param command
* @param loanProductBorrowerCycleVariations
*/
private static void assembleInterestRateVariations(final JsonCommand command,
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
assembleVaritions(command, loanProductBorrowerCycleVariations, LoanProductParamType.INTERESTRATE.getValue(),
LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName);
}
/**
* @param command
* @param loanProductBorrowerCycleVariations
*/
private static void assembleRepaymentVariations(final JsonCommand command,
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
assembleVaritions(command, loanProductBorrowerCycleVariations, LoanProductParamType.REPAYMENT.getValue(),
LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName);
}
/**
* @param command
* @param loanProductBorrowerCycleVariations
*/
private static void assemblePrincipalVariations(final JsonCommand command,
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
assembleVaritions(command, loanProductBorrowerCycleVariations, LoanProductParamType.PRINCIPAL.getValue(),
LoanProductConstants.principalVariationsForBorrowerCycleParameterName);
}
private static void assembleVaritions(final JsonCommand command,
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations,Integer paramType,
String variationParameterName){
if (command.parameterExists(variationParameterName)) {
final JsonArray variationArray= command.arrayOfParameterNamed(variationParameterName);
if (variationArray != null && variationArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
BigDecimal defaultValue = null;
BigDecimal minValue = null;
BigDecimal maxValue = null;
Integer cycleNumber = null;
Integer valueUsageCondition = null;
if (jsonObject.has(LoanProductConstants.defaultValueParameterName) && jsonObject.get(LoanProductConstants.defaultValueParameterName).isJsonPrimitive()) {
defaultValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.defaultValueParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanProductConstants.minValueParameterName) && jsonObject.get(LoanProductConstants.minValueParameterName).isJsonPrimitive()) {
minValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.minValueParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanProductConstants.maxValueParameterName) && jsonObject.get(LoanProductConstants.maxValueParameterName).isJsonPrimitive()) {
maxValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.maxValueParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanProductConstants.borrowerCycleNumberParamName) && jsonObject.get(LoanProductConstants.borrowerCycleNumberParamName).isJsonPrimitive()) {
cycleNumber = jsonObject.getAsJsonPrimitive(LoanProductConstants.borrowerCycleNumberParamName).getAsInt();
}
if (jsonObject.has(LoanProductConstants.valueConditionTypeParamName) && jsonObject.get(LoanProductConstants.valueConditionTypeParamName).isJsonPrimitive()) {
valueUsageCondition = jsonObject.getAsJsonPrimitive(LoanProductConstants.valueConditionTypeParamName).getAsInt();
}
LoanProductBorrowerCycleVariations borrowerCycleVariations= new LoanProductBorrowerCycleVariations(cycleNumber, paramType, valueUsageCondition, minValue, maxValue, defaultValue);
loanProductBorrowerCycleVariations.add(borrowerCycleVariations);
i++;
}while (i < variationArray.size());
}
}
}
private Map<String, Object> updateBorrowerCycleVariations(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(20);
List<Long> variationIds = fetchAllVariationIds();
updateBorrowerCycleVaritions(command, LoanProductParamType.PRINCIPAL.getValue(), LoanProductConstants.principalVariationsForBorrowerCycleParameterName, actualChanges, variationIds);
updateBorrowerCycleVaritions(command, LoanProductParamType.INTERESTRATE.getValue(), LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName, actualChanges, variationIds);
updateBorrowerCycleVaritions(command, LoanProductParamType.REPAYMENT.getValue(), LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName, actualChanges, variationIds);
for(Long id:variationIds){
this.borrowerCycleVariations.remove(fetchLoanProductBorrowerCycleVariationById(id));
}
return actualChanges;
}
private List<Long> fetchAllVariationIds(){
List<Long> list = new ArrayList<Long>();
for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) {
list.add(cycleVariation.getId());
}
return list;
}
private void updateBorrowerCycleVaritions(final JsonCommand command,Integer paramType,
String variationParameterName,final Map<String, Object> actualChanges, List<Long> variationIds){
if (command.parameterExists(variationParameterName)) {
final JsonArray variationArray= command.arrayOfParameterNamed(variationParameterName);
if (variationArray != null && variationArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
BigDecimal defaultValue = null;
BigDecimal minValue = null;
BigDecimal maxValue = null;
Integer cycleNumber = null;
Integer valueUsageCondition = null;
Long id = null;
if (jsonObject.has(LoanProductConstants.defaultValueParameterName) && jsonObject.get(LoanProductConstants.defaultValueParameterName).isJsonPrimitive()) {
defaultValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.defaultValueParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanProductConstants.minValueParameterName) && jsonObject.get(LoanProductConstants.minValueParameterName).isJsonPrimitive()) {
minValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.minValueParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanProductConstants.maxValueParameterName) && jsonObject.get(LoanProductConstants.maxValueParameterName).isJsonPrimitive()) {
maxValue = jsonObject.getAsJsonPrimitive(LoanProductConstants.maxValueParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanProductConstants.borrowerCycleNumberParamName) && jsonObject.get(LoanProductConstants.borrowerCycleNumberParamName).isJsonPrimitive()) {
cycleNumber = jsonObject.getAsJsonPrimitive(LoanProductConstants.borrowerCycleNumberParamName).getAsInt();
}
if (jsonObject.has(LoanProductConstants.valueConditionTypeParamName) && jsonObject.get(LoanProductConstants.valueConditionTypeParamName).isJsonPrimitive()) {
valueUsageCondition = jsonObject.getAsJsonPrimitive(LoanProductConstants.valueConditionTypeParamName).getAsInt();
}
if (jsonObject.has(LoanProductConstants.borrowerCycleIdParameterName) && jsonObject.get(LoanProductConstants.borrowerCycleIdParameterName).isJsonPrimitive()) {
id = jsonObject.getAsJsonPrimitive(LoanProductConstants.borrowerCycleIdParameterName).getAsLong();
}
LoanProductBorrowerCycleVariations borrowerCycleVariations= new LoanProductBorrowerCycleVariations(cycleNumber, paramType, valueUsageCondition, minValue, maxValue, defaultValue);
if(id == null){
borrowerCycleVariations.updateLoanProduct(this);
this.borrowerCycleVariations.add(borrowerCycleVariations);
actualChanges.put("borrowerCycleParamType", paramType);
}else{
variationIds.remove(id);
LoanProductBorrowerCycleVariations existingCycleVariation = fetchLoanProductBorrowerCycleVariationById(id);
if(!existingCycleVariation.equals(borrowerCycleVariations)){
existingCycleVariation.copy(borrowerCycleVariations);
actualChanges.put("borrowerCycleId", id);
}
}
i++;
}while (i < variationArray.size());
}
}
}
private void clearVariations(LoanProductParamType paramType, boolean clearAll) {
if (clearAll) {
this.borrowerCycleVariations.clear();
} else {
Set<LoanProductBorrowerCycleVariations> remove = new HashSet<LoanProductBorrowerCycleVariations>();
for (LoanProductBorrowerCycleVariations borrowerCycleVariations : this.borrowerCycleVariations) {
if (paramType.equals(borrowerCycleVariations.getParamType())) {
remove.add(borrowerCycleVariations);
}
}
this.borrowerCycleVariations.removeAll(remove);
}
}
protected LoanProduct() {
@@ -162,7 +352,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List<Charge> charges,
final AccountingRuleType accountingRuleType, final boolean includeInBorrowerCycle, final LocalDate startDate,
final LocalDate closeDate, final String externalId) {
final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle,
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -191,6 +382,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.accountingRule = accountingRuleType.getValue();
}
this.includeInBorrowerCycle = includeInBorrowerCycle;
this.useBorrowerCycle = useBorrowerCycle;
if (startDate != null) {
this.startDate = startDate.toDateTimeAtStartOfDay().toDate();
@@ -201,6 +393,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
}
this.externalId = externalId;
this.borrowerCycleVariations = loanProductBorrowerCycleVariations;
for (LoanProductBorrowerCycleVariations borrowerCycleVariations : this.borrowerCycleVariations) {
borrowerCycleVariations.updateLoanProduct(this);
}
}
public MonetaryCurrency getCurrency() {
@@ -303,6 +499,18 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.includeInBorrowerCycle = newValue;
}
final String useBorrowerCycleParamName = "useBorrowerCycle";
if (command.isChangeInBooleanParameterNamed(useBorrowerCycleParamName, this.includeInBorrowerCycle)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(useBorrowerCycleParamName);
actualChanges.put(useBorrowerCycleParamName, newValue);
this.useBorrowerCycle = newValue;
}
if (this.useBorrowerCycle) {
actualChanges.putAll(updateBorrowerCycleVariations(command));
}else{
clearVariations(null, true);
}
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
@@ -401,8 +609,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
// If all min and max fields are null then loanProductMinMaxConstraints
// initialising to null
// Reset LoanProductMinMaxConstraints with null values.
this.loanProductMinMaxConstraints = this.loanProductMinMaxConstraints == null ? new LoanProductMinMaxConstraints(null, null,
null, null, null, null) : this.loanProductMinMaxConstraints;
this.loanProductMinMaxConstraints = this.loanProductMinMaxConstraints == null ? new LoanProductMinMaxConstraints(null, null, null,
null, null, null) : this.loanProductMinMaxConstraints;
return this.loanProductMinMaxConstraints;
}
@@ -434,4 +642,100 @@ public class LoanProduct extends AbstractPersistable<Long> {
return this.externalId;
}
public boolean useBorrowerCycle() {
return this.useBorrowerCycle;
}
public LoanProductBorrowerCycleVariations fetchLoanProductBorrowerCycleVariationById(Long id){
LoanProductBorrowerCycleVariations borrowerCycleVariation = null;
for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) {
if(id.equals(cycleVariation.getId())){
borrowerCycleVariation = cycleVariation;
break;
}
}
return borrowerCycleVariation;
}
public Map<String, BigDecimal> fetchBorrowerCycleVariationsForCycleNumber(final Integer cycleNumber) {
Map<String, BigDecimal> borrowerCycleVariations = new HashMap<String, BigDecimal>();
borrowerCycleVariations.put(LoanProductConstants.principal, this.loanProductRelatedDetail.getPrincipal().getAmount());
borrowerCycleVariations.put(LoanProductConstants.minPrincipal, this.loanProductMinMaxConstraints.getMinPrincipal());
borrowerCycleVariations.put(LoanProductConstants.maxPrincipal, this.loanProductMinMaxConstraints.getMaxPrincipal());
borrowerCycleVariations.put(LoanProductConstants.interestRatePerPeriod,
this.loanProductRelatedDetail.getNominalInterestRatePerPeriod());
borrowerCycleVariations.put(LoanProductConstants.minInterestRatePerPeriod,
this.loanProductMinMaxConstraints.getMinNominalInterestRatePerPeriod());
borrowerCycleVariations.put(LoanProductConstants.maxInterestRatePerPeriod,
this.loanProductMinMaxConstraints.getMaxNominalInterestRatePerPeriod());
borrowerCycleVariations.put(LoanProductConstants.numberOfRepayments,
BigDecimal.valueOf(this.loanProductRelatedDetail.getNumberOfRepayments()));
borrowerCycleVariations.put(LoanProductConstants.minNumberOfRepayments,
BigDecimal.valueOf(this.loanProductMinMaxConstraints.getMinNumberOfRepayments()));
borrowerCycleVariations.put(LoanProductConstants.maxNumberOfRepayments,
BigDecimal.valueOf(this.loanProductMinMaxConstraints.getMaxNumberOfRepayments()));
if (cycleNumber > 0) {
Integer principalCycleUsed = 0;
Integer interestCycleUsed = 0;
Integer repaymentCycleUsed = 0;
for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) {
if (cycleVariation.getBorrowerCycleNumber() == cycleNumber
&& cycleVariation.getValueConditionType().equals(LoanProductValueConditionType.EQUAL)) {
switch (cycleVariation.getParamType()) {
case PRINCIPAL:
borrowerCycleVariations.put(LoanProductConstants.principal, cycleVariation.getDefaultValue());
borrowerCycleVariations.put(LoanProductConstants.minPrincipal, cycleVariation.getMinValue());
borrowerCycleVariations.put(LoanProductConstants.maxPrincipal, cycleVariation.getMaxValue());
principalCycleUsed = cycleVariation.getBorrowerCycleNumber();
break;
case INTERESTRATE:
borrowerCycleVariations.put(LoanProductConstants.interestRatePerPeriod, cycleVariation.getDefaultValue());
borrowerCycleVariations.put(LoanProductConstants.minInterestRatePerPeriod, cycleVariation.getMinValue());
borrowerCycleVariations.put(LoanProductConstants.maxInterestRatePerPeriod, cycleVariation.getMaxValue());
interestCycleUsed = cycleVariation.getBorrowerCycleNumber();
break;
case REPAYMENT:
borrowerCycleVariations.put(LoanProductConstants.numberOfRepayments, cycleVariation.getDefaultValue());
borrowerCycleVariations.put(LoanProductConstants.minNumberOfRepayments, cycleVariation.getMinValue());
borrowerCycleVariations.put(LoanProductConstants.maxNumberOfRepayments, cycleVariation.getMaxValue());
repaymentCycleUsed = cycleVariation.getBorrowerCycleNumber();
break;
default:
break;
}
} else if (cycleVariation.getBorrowerCycleNumber() < cycleNumber
&& cycleVariation.getValueConditionType().equals(LoanProductValueConditionType.GRETERTHAN)) {
switch (cycleVariation.getParamType()) {
case PRINCIPAL:
if(principalCycleUsed < cycleVariation.getBorrowerCycleNumber()){
borrowerCycleVariations.put(LoanProductConstants.principal, cycleVariation.getDefaultValue());
borrowerCycleVariations.put(LoanProductConstants.minPrincipal, cycleVariation.getMinValue());
borrowerCycleVariations.put(LoanProductConstants.maxPrincipal, cycleVariation.getMaxValue());
principalCycleUsed = cycleVariation.getBorrowerCycleNumber();
}
break;
case INTERESTRATE:
if(interestCycleUsed < cycleVariation.getBorrowerCycleNumber()){
borrowerCycleVariations.put(LoanProductConstants.interestRatePerPeriod, cycleVariation.getDefaultValue());
borrowerCycleVariations.put(LoanProductConstants.minInterestRatePerPeriod, cycleVariation.getMinValue());
borrowerCycleVariations.put(LoanProductConstants.maxInterestRatePerPeriod, cycleVariation.getMaxValue());
interestCycleUsed = cycleVariation.getBorrowerCycleNumber();
}
break;
case REPAYMENT:
if(repaymentCycleUsed < cycleVariation.getBorrowerCycleNumber()){
borrowerCycleVariations.put(LoanProductConstants.numberOfRepayments, cycleVariation.getDefaultValue());
borrowerCycleVariations.put(LoanProductConstants.minNumberOfRepayments, cycleVariation.getMinValue());
borrowerCycleVariations.put(LoanProductConstants.maxNumberOfRepayments, cycleVariation.getMaxValue());
repaymentCycleUsed = cycleVariation.getBorrowerCycleNumber();
}
break;
default:
break;
}
}
}
}
return borrowerCycleVariations;
}
}
@@ -0,0 +1,123 @@
/**
* 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.loanproduct.domain;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_product_loan_variations_borrower_cycle")
public class LoanProductBorrowerCycleVariations extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "loan_product_id", nullable = false)
private LoanProduct loanProduct;
@Column(name = "borrower_cycle_number", nullable = false)
private Integer borrowerCycleNumber;
@Column(name = "param_type", nullable = false)
private Integer paramType;
@Column(name = "value_condition", nullable = false)
private Integer valueConditionType;
@Column(name = "min_value", scale = 6, precision = 19, nullable = true)
private BigDecimal minValue;
@Column(name = "max_value", scale = 6, precision = 19, nullable = true)
private BigDecimal maxValue;
@Column(name = "default_value", scale = 6, precision = 19, nullable = false)
private BigDecimal defaultValue;
protected LoanProductBorrowerCycleVariations() {
}
public LoanProductBorrowerCycleVariations(final Integer borrowerCycleNumber, final Integer paramType, final Integer valueConditionType,
final BigDecimal minValue, final BigDecimal maxValue, final BigDecimal defaultValue) {
this.borrowerCycleNumber = borrowerCycleNumber;
this.paramType = paramType;
this.valueConditionType = valueConditionType;
this.minValue = minValue;
this.maxValue = maxValue;
this.defaultValue = defaultValue;
}
public void updateLoanProduct(final LoanProduct loanProduct) {
this.loanProduct = loanProduct;
}
public LoanProductParamType getParamType() {
return LoanProductParamType.fromInt(this.paramType);
}
public LoanProductValueConditionType getValueConditionType() {
return LoanProductValueConditionType.fromInt(this.valueConditionType);
}
@Override
public boolean equals(Object obj) {
LoanProductBorrowerCycleVariations borrowerCycleVariations = (LoanProductBorrowerCycleVariations) obj;
boolean minValequal = false;
if(borrowerCycleVariations.minValue == null && this.minValue == null){
minValequal = true;
}else if(borrowerCycleVariations.minValue != null && this.minValue != null){
minValequal = borrowerCycleVariations.minValue.equals(this.minValue);
}
boolean maxValequal = false;
if(borrowerCycleVariations.maxValue == null && this.maxValue == null){
maxValequal = true;
}else if(borrowerCycleVariations.maxValue != null && this.maxValue != null){
maxValequal = borrowerCycleVariations.maxValue.equals(this.maxValue);
}
if (borrowerCycleVariations.borrowerCycleNumber.equals(this.borrowerCycleNumber)
&& borrowerCycleVariations.defaultValue.equals(this.defaultValue)
&& minValequal && maxValequal
&& borrowerCycleVariations.valueConditionType.equals(this.valueConditionType)
&& borrowerCycleVariations.paramType.equals(this.paramType)) { return true; }
return false;
}
public void copy(LoanProductBorrowerCycleVariations borrowerCycleVariations){
this.defaultValue = borrowerCycleVariations.defaultValue;
this.minValue = borrowerCycleVariations.minValue;
this.maxValue = borrowerCycleVariations.maxValue;
this.valueConditionType = borrowerCycleVariations.valueConditionType;
this.borrowerCycleNumber = borrowerCycleVariations.borrowerCycleNumber;
}
public Integer getBorrowerCycleNumber() {
return this.borrowerCycleNumber;
}
public BigDecimal getMinValue() {
return this.minValue;
}
public BigDecimal getMaxValue() {
return this.maxValue;
}
public BigDecimal getDefaultValue() {
return this.defaultValue;
}
}
@@ -0,0 +1,64 @@
/**
* 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.loanproduct.domain;
public enum LoanProductParamType {
INVALID(0, "LoanProductParamType.invalid"), //
PRINCIPAL(1, "LoanProductParamType.principal"), //
INTERESTRATE(2, "LoanProductParamType.interestrate"), //
REPAYMENT(3, "LoanProductParamType.repayment"); //
private final Integer value;
private final String code;
private LoanProductParamType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static LoanProductParamType fromInt(final Integer chargeTime) {
LoanProductParamType loanProductParamType = LoanProductParamType.INVALID;
if (chargeTime != null) {
switch (chargeTime) {
case 1:
loanProductParamType = PRINCIPAL;
break;
case 2:
loanProductParamType = INTERESTRATE;
break;
case 3:
loanProductParamType = REPAYMENT;
break;
default:
loanProductParamType = INVALID;
break;
}
}
return loanProductParamType;
}
public boolean isParamTypePrincipal(){
return LoanProductParamType.PRINCIPAL.getValue().equals(this.value);
}
public boolean isParamTypeInterestTate(){
return LoanProductParamType.INTERESTRATE.getValue().equals(this.value);
}
public boolean isParamTypeRepayment(){
return LoanProductParamType.REPAYMENT.getValue().equals(this.value);
}
}
@@ -0,0 +1,57 @@
/**
* 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.loanproduct.domain;
public enum LoanProductValueConditionType {
INVALID(0, "LoanProductParamType.invalid"), //
// LESSTHAN(1,"LoanProductValueConditionType.lessthan"),//
EQUAL(2, "LoanProductValueConditionType.equal"), //
GRETERTHAN(3, "LoanProductValueConditionType.greterthan"); //
private final Integer value;
private final String code;
private LoanProductValueConditionType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static LoanProductValueConditionType fromInt(final Integer type) {
LoanProductValueConditionType loanProductParamType = LoanProductValueConditionType.INVALID;
if (type != null) {
switch (type) {
case 2:
loanProductParamType = EQUAL;
break;
case 3:
loanProductParamType = GRETERTHAN;
break;
default:
loanProductParamType = INVALID;
break;
}
}
return loanProductParamType;
}
public boolean isValueConditionTypeEqual(){
return LoanProductValueConditionType.EQUAL.getValue().equals(this.value);
}
public boolean isValueConditionTypeGreterThan(){
return LoanProductValueConditionType.GRETERTHAN.getValue().equals(this.value);
}
}
@@ -23,7 +23,9 @@ 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.loanproduct.LoanProductConstants;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -52,7 +54,9 @@ public final class LoanProductDataValidator {
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue()));
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),LoanProductConstants.useBorrowerCycleParameterName,
LoanProductConstants.principalVariationsForBorrowerCycleParameterName,LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName,
LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName));
private final FromJsonHelper fromApiJsonHelper;
@@ -309,6 +313,15 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue())
.value(receivablePenaltyAccountId).notNull().integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.useBorrowerCycleParameterName, element)) {
final Boolean useBorrowerCycle = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.useBorrowerCycleParameterName, element);
baseDataValidator.reset().parameter(LoanProductConstants.useBorrowerCycleParameterName).value(useBorrowerCycle).ignoreIfNull()
.validateForBooleanValue();
}
validateBorrowerCycleVariations(element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -550,6 +563,14 @@ public final class LoanProductDataValidator {
validateMinMaxConstraints(element, baseDataValidator, loanProduct);
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.useBorrowerCycleParameterName, element)) {
final Boolean useBorrowerCycle = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.useBorrowerCycleParameterName, element);
baseDataValidator.reset().parameter(LoanProductConstants.useBorrowerCycleParameterName).value(useBorrowerCycle).ignoreIfNull()
.validateForBooleanValue();
}
validateBorrowerCycleVariations(element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -633,6 +654,87 @@ public final class LoanProductDataValidator {
validateNominalInterestRatePerPeriodMinMaxConstraint(element, loanProduct, baseDataValidator);
}
public void validateMinMaxConstraints(final JsonElement element, final DataValidatorBuilder baseDataValidator,
final LoanProduct loanProduct,Integer cycleNumber) {
final Map<String, BigDecimal> minmaxValues = loanProduct.fetchBorrowerCycleVariationsForCycleNumber(cycleNumber);
final String principalParameterName = "principal";
BigDecimal principalAmount = null;
BigDecimal minPrincipalAmount = null;
BigDecimal maxPrincipalAmount = null;
if (this.fromApiJsonHelper.parameterExists(principalParameterName, element)) {
principalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
minPrincipalAmount = minmaxValues.get(LoanProductConstants.minPrincipal);
maxPrincipalAmount = minmaxValues.get(LoanProductConstants.maxPrincipal);
}
if ((minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) == 1)
&& (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) == 1)) {
baseDataValidator.reset().parameter(principalParameterName).value(principalAmount)
.inMinAndMaxAmountRange(minPrincipalAmount, maxPrincipalAmount);
} else {
if (minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) == 1) {
baseDataValidator.reset().parameter(principalParameterName).value(principalAmount).notLessThanMin(minPrincipalAmount);
} else if (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) == 1) {
baseDataValidator.reset().parameter(principalParameterName).value(principalAmount)
.notGreaterThanMax(maxPrincipalAmount);
}
}
final String numberOfRepaymentsParameterName = "numberOfRepayments";
Integer maxNumberOfRepayments = null;
Integer minNumberOfRepayments = null;
Integer numberOfRepayments = null;
if (this.fromApiJsonHelper.parameterExists(numberOfRepaymentsParameterName, element)) {
numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element);
if(minmaxValues.get(LoanProductConstants.minNumberOfRepayments)!=null){
minNumberOfRepayments = minmaxValues.get(LoanProductConstants.minNumberOfRepayments).intValueExact();
}
if(minmaxValues.get(LoanProductConstants.maxNumberOfRepayments)!=null){
maxNumberOfRepayments = minmaxValues.get(LoanProductConstants.maxNumberOfRepayments).intValueExact();
}
}
if (maxNumberOfRepayments != null && maxNumberOfRepayments.compareTo(0) == 1) {
if (minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1) {
baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
.inMinMaxRange(minNumberOfRepayments, maxNumberOfRepayments);
} else {
baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
.notGreaterThanMax(maxNumberOfRepayments);
}
} else if (minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1) {
baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments)
.notLessThanMin(minNumberOfRepayments);
}
final String interestRatePerPeriodParameterName = "interestRatePerPeriod";
BigDecimal interestRatePerPeriod = null;
BigDecimal minInterestRatePerPeriod = null;
BigDecimal maxInterestRatePerPeriod = null;
if (this.fromApiJsonHelper.parameterExists(interestRatePerPeriodParameterName, element)) {
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, element);
minInterestRatePerPeriod = minmaxValues.get(LoanProductConstants.minInterestRatePerPeriod);
maxInterestRatePerPeriod = minmaxValues.get(LoanProductConstants.maxInterestRatePerPeriod);
}
if (maxInterestRatePerPeriod != null) {
if (minInterestRatePerPeriod != null) {
baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
.inMinAndMaxAmountRange(minInterestRatePerPeriod, maxInterestRatePerPeriod);
} else {
baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
.notGreaterThanMax(maxInterestRatePerPeriod);
}
} else if (minInterestRatePerPeriod != null) {
baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod)
.notLessThanMin(minInterestRatePerPeriod);
}
}
private void validatePrincipalMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct,
final DataValidatorBuilder baseDataValidator) {
@@ -825,4 +927,111 @@ public final class LoanProductDataValidator {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
private void validateBorrowerCycleVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
validateBorrowerCyclePrincipalVariations(element, baseDataValidator);
validateBorrowerCycleRepaymentVariations(element, baseDataValidator);
validateBorrowerCycleInterestVariations(element, baseDataValidator);
}
private void validateBorrowerCyclePrincipalVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
validateBorrowerCycleVariations(element, baseDataValidator, LoanProductConstants.principalVariationsForBorrowerCycleParameterName,
LoanProductConstants.principalPerCycleParameterName, LoanProductConstants.minPrincipalPerCycleParameterName,
LoanProductConstants.maxPrincipalPerCycleParameterName, LoanProductConstants.principalValueUsageConditionParamName,
LoanProductConstants.principalCycleNumbersParamName);
}
private void validateBorrowerCycleRepaymentVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
validateBorrowerCycleVariations(element, baseDataValidator, LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName,
LoanProductConstants.numberOfRepaymentsPerCycleParameterName, LoanProductConstants.minNumberOfRepaymentsPerCycleParameterName,
LoanProductConstants.maxNumberOfRepaymentsPerCycleParameterName, LoanProductConstants.repaymentValueUsageConditionParamName,
LoanProductConstants.repaymentCycleNumberParamName);
}
private void validateBorrowerCycleInterestVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
validateBorrowerCycleVariations(element, baseDataValidator, LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName,
LoanProductConstants.interestRatePerPeriodPerCycleParameterName,LoanProductConstants.minInterestRatePerPeriodPerCycleParameterName,
LoanProductConstants.maxInterestRatePerPeriodPerCycleParameterName,LoanProductConstants.interestRateValueUsageConditionParamName,
LoanProductConstants.interestRateCycleNumberParamName);
}
private void validateBorrowerCycleVariations(final JsonElement element, final DataValidatorBuilder baseDataValidator,
final String variationParameterName,final String defaultParameterName,final String minParameterName,
final String maxParameterName,final String valueUsageConditionParamName,final String cycleNumbersParamName) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
Integer lastCycleNumber = 0;
LoanProductValueConditionType lastConditionType = LoanProductValueConditionType.EQUAL;
if (this.fromApiJsonHelper.parameterExists(variationParameterName, element)) {
final JsonArray variationArray= this.fromApiJsonHelper.extractJsonArrayNamed(variationParameterName, element);
if (variationArray != null && variationArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
BigDecimal defaultValue = this.fromApiJsonHelper.extractBigDecimalNamed(
LoanProductConstants.defaultValueParameterName, jsonObject,locale);
BigDecimal minValue = this.fromApiJsonHelper.extractBigDecimalNamed(
LoanProductConstants.minValueParameterName, jsonObject,locale);
BigDecimal maxValue = this.fromApiJsonHelper.extractBigDecimalNamed(
LoanProductConstants.maxValueParameterName, jsonObject,locale);
Integer cycleNumber = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.borrowerCycleNumberParamName, jsonObject, locale);
Integer valueUsageCondition = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.valueConditionTypeParamName, jsonObject, locale);
baseDataValidator.reset().parameter(defaultParameterName).value(defaultValue).notBlank();
if (minValue != null) {
baseDataValidator.reset().parameter(minParameterName).value(minValue).notGreaterThanMax(maxValue);
}
if (maxValue != null) {
baseDataValidator.reset().parameter(maxParameterName).value(maxValue).notLessThanMin(minValue);
}
if ((minValue != null && minValue.compareTo(BigDecimal.ZERO) == 1)
&& (maxValue != null && maxValue.compareTo(BigDecimal.ZERO) == 1)) {
baseDataValidator.reset().parameter(defaultParameterName).value(defaultValue)
.inMinAndMaxAmountRange(minValue, maxValue);
} else {
if (minValue != null && minValue.compareTo(BigDecimal.ZERO) == 1) {
baseDataValidator.reset().parameter(defaultParameterName).value(defaultValue).notLessThanMin(minValue);
} else if (maxValue != null && maxValue.compareTo(BigDecimal.ZERO) == 1) {
baseDataValidator.reset().parameter(defaultParameterName).value(defaultValue).notGreaterThanMax(maxValue);
}
}
LoanProductValueConditionType conditionType = LoanProductValueConditionType.INVALID;
if (valueUsageCondition != null) {
conditionType = LoanProductValueConditionType.fromInt(valueUsageCondition);
}
baseDataValidator.reset().parameter(valueUsageConditionParamName).value(valueUsageCondition).notNull().inMinMaxRange(LoanProductValueConditionType.EQUAL.getValue(),
LoanProductValueConditionType.GRETERTHAN.getValue());
if (lastConditionType.equals(LoanProductValueConditionType.EQUAL)
&& conditionType.equals(LoanProductValueConditionType.GRETERTHAN)) {
baseDataValidator.reset().parameter(cycleNumbersParamName).value(cycleNumber).notNull()
.integerSameAsNumber(lastCycleNumber);
} else if (lastConditionType.equals(LoanProductValueConditionType.EQUAL)) {
baseDataValidator.reset().parameter(cycleNumbersParamName).value(cycleNumber).notNull()
.integerSameAsNumber(lastCycleNumber+1);
} else if (lastConditionType.equals(LoanProductValueConditionType.GRETERTHAN)) {
baseDataValidator.reset().parameter(cycleNumbersParamName).value(cycleNumber).notNull()
.integerGreaterThanNumber(lastCycleNumber);
}
if(conditionType!=null){
lastConditionType = conditionType;
}
if(cycleNumber!=null){
lastCycleNumber = cycleNumber;
}
i++;
} while (i < variationArray.size());
if(!lastConditionType.equals(LoanProductValueConditionType.GRETERTHAN)){
baseDataValidator.reset().parameter(cycleNumbersParamName).failWithCode(LoanProductConstants.VALUE_CONDITION_TYPE_ERROR);
}
}
}
}
}
@@ -27,4 +27,6 @@ public interface LoanDropdownReadPlatformService {
Collection<TransactionProcessingStrategyData> retreiveTransactionProcessingStrategies();
List<EnumOptionData> retrieveLoanCycleValueConditionTypeOptions();
}
@@ -11,6 +11,7 @@ import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.i
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestType;
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.loanTermFrequencyType;
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyType;
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.loanCycleValueConditionType;
import java.util.ArrayList;
import java.util.Arrays;
@@ -23,6 +24,7 @@ import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrateg
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.springframework.beans.factory.annotation.Autowired;
@@ -100,5 +102,14 @@ public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlat
return strategyOptions;
}
@Override
public List<EnumOptionData> retrieveLoanCycleValueConditionTypeOptions() {
final List<EnumOptionData> repaymentFrequencyOptions = Arrays.asList(loanCycleValueConditionType(LoanProductValueConditionType.EQUAL),
loanCycleValueConditionType(LoanProductValueConditionType.GRETERTHAN));
return repaymentFrequencyOptions;
}
}
@@ -14,6 +14,8 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public class LoanEnumerations {
@@ -369,4 +371,50 @@ public class LoanEnumerations {
return optionData;
}
public static EnumOptionData loanCycleValueConditionType(final int id) {
return loanCycleValueConditionType(LoanProductValueConditionType.fromInt(id));
}
public static EnumOptionData loanCycleValueConditionType(final LoanProductValueConditionType type) {
EnumOptionData optionData = null;
switch (type) {
case EQUAL:
optionData = new EnumOptionData(LoanProductValueConditionType.EQUAL.getValue().longValue(), LoanProductValueConditionType.EQUAL.getCode(), "equals");
break;
case GRETERTHAN:
optionData = new EnumOptionData(LoanProductValueConditionType.GRETERTHAN.getValue().longValue(), LoanProductValueConditionType.GRETERTHAN.getCode(), "greter than");
break;
default:
optionData = new EnumOptionData(LoanProductValueConditionType.INVALID.getValue().longValue(), LoanProductValueConditionType.INVALID.getCode(),
"Invalid");
break;
}
return optionData;
}
public static EnumOptionData loanCycleParamType(final int id) {
return loanCycleParamType(LoanProductParamType.fromInt(id));
}
public static EnumOptionData loanCycleParamType(final LoanProductParamType type) {
EnumOptionData optionData = null;
switch (type) {
case PRINCIPAL:
optionData = new EnumOptionData(LoanProductParamType.PRINCIPAL.getValue().longValue(), LoanProductParamType.PRINCIPAL.getCode(), "principal");
break;
case INTERESTRATE:
optionData = new EnumOptionData(LoanProductParamType.INTERESTRATE.getValue().longValue(),LoanProductParamType.INTERESTRATE.getCode(), "Interest rate");
break;
case REPAYMENT:
optionData = new EnumOptionData(LoanProductParamType.REPAYMENT.getValue().longValue(),LoanProductParamType.REPAYMENT.getCode(), "repayment");
break;
default:
optionData = new EnumOptionData(LoanProductParamType.INVALID.getValue().longValue(), LoanProductParamType.INVALID.getCode(),
"Invalid");
break;
}
return optionData;
}
}
@@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanproduct.service;
import java.util.Collection;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
public interface LoanProductReadPlatformService {
@@ -24,4 +25,6 @@ public interface LoanProductReadPlatformService {
Collection<LoanProductData> retrieveRestrictedProductsForMix(Long productId);
Collection<LoanProductData> retrieveAllowedProductsForMix(Long productId);
Collection<LoanProductBorrowerCycleVariationData> retrieveLoanProductBorrowerCycleVariations(Long loanProductId);
}
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanproduct.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import org.joda.time.LocalDate;
@@ -20,7 +21,9 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -48,8 +51,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
try {
final Collection<ChargeData> charges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanProductId);
final LoanProductMapper rm = new LoanProductMapper(charges);
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas = this.retrieveLoanProductBorrowerCycleVariations(loanProductId);
final LoanProductMapper rm = new LoanProductMapper(charges,borrowerCycleVariationDatas);
final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanProductId });
@@ -58,13 +61,20 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
throw new LoanProductNotFoundException(loanProductId);
}
}
@Override
public Collection<LoanProductBorrowerCycleVariationData> retrieveLoanProductBorrowerCycleVariations(final Long loanProductId) {
LoanProductBorrowerCycleMapper rm = new LoanProductBorrowerCycleMapper();
String sql = "select " + rm.schema() +" where bc.loan_product_id=? order by bc.borrower_cycle_number,bc.value_condition";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanProductId });
}
@Override
public Collection<LoanProductData> retrieveAllLoanProducts() {
this.context.authenticatedUser();
final LoanProductMapper rm = new LoanProductMapper(null);
final LoanProductMapper rm = new LoanProductMapper(null,null);
final String sql = "select " + rm.loanProductSchema();
@@ -91,9 +101,12 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
private static final class LoanProductMapper implements RowMapper<LoanProductData> {
private final Collection<ChargeData> charges;
private final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas;
public LoanProductMapper(final Collection<ChargeData> charges) {
public LoanProductMapper(final Collection<ChargeData> charges,final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas) {
this.charges = charges;
this.borrowerCycleVariationDatas = borrowerCycleVariationDatas;
}
public String loanProductSchema() {
@@ -105,7 +118,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, "
+ "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,"
+ "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, "
+ "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, "
+ "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, "
+ "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId "
+ " from m_product_loan lp "
+ " left join m_fund f on f.id = lp.fund_id"
@@ -173,6 +186,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
.interestCalculationPeriodType(interestCalculationPeriodTypeId);
final boolean includeInBorrowerCycle = rs.getBoolean("includeInBorrowerCycle");
final boolean useBorrowerCycle = rs.getBoolean("useBorrowerCycle");
final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
final LocalDate closeDate = JdbcSupport.getLocalDate(rs, "closeDate");
String status = "";
@@ -182,14 +196,29 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
status = "loanProduct.active";
}
final String externalId = rs.getString("externalId");
Collection<LoanProductBorrowerCycleVariationData> principalVariationsForBorrowerCycle = new ArrayList<LoanProductBorrowerCycleVariationData>();;
Collection<LoanProductBorrowerCycleVariationData> interestRateVariationsForBorrowerCycle = new ArrayList<LoanProductBorrowerCycleVariationData>();;
Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<LoanProductBorrowerCycleVariationData>();;
if(this.borrowerCycleVariationDatas!=null){
for(LoanProductBorrowerCycleVariationData borrowerCycleVariationData:this.borrowerCycleVariationDatas){
LoanProductParamType loanProductParamType =borrowerCycleVariationData.getParamType();
if(loanProductParamType.isParamTypePrincipal()){
principalVariationsForBorrowerCycle.add(borrowerCycleVariationData);
}else if(loanProductParamType.isParamTypeInterestTate()){
interestRateVariationsForBorrowerCycle.add(borrowerCycleVariationData);
}else if(loanProductParamType.isParamTypeRepayment()){
numberOfRepaymentVariationsForBorrowerCycle.add(borrowerCycleVariationData);
}
}
}
return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName,
transactionStrategyId, transactionStrategyName, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate, status,
externalId);
graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate,
status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle);
}
}
@@ -222,6 +251,31 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
return LoanProductData.lookup(id, name);
}
}
private static final class LoanProductBorrowerCycleMapper implements RowMapper<LoanProductBorrowerCycleVariationData> {
public String schema(){
return "bc.id as id,bc.borrower_cycle_number as cycleNumber,bc.value_condition as conditionType,bc.param_type as paramType," +
"bc.default_value as defaultValue,bc.max_value as maxVal,bc.min_value as minVal "+
"from m_product_loan_variations_borrower_cycle bc";
}
@Override
public LoanProductBorrowerCycleVariationData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Integer cycleNumber = JdbcSupport.getInteger(rs, "cycleNumber");
final Integer conditionType = JdbcSupport.getInteger(rs, "conditionType");
final EnumOptionData conditionTypeData = LoanEnumerations.loanCycleValueConditionType(conditionType);
final Integer paramType = JdbcSupport.getInteger(rs, "paramType");
final EnumOptionData paramTypeData = LoanEnumerations.loanCycleParamType(paramType);
final BigDecimal defaultValue = rs.getBigDecimal("defaultValue");
final BigDecimal maxValue = rs.getBigDecimal("maxVal");
final BigDecimal minValue = rs.getBigDecimal("minVal");
LoanProductBorrowerCycleVariationData borrowerCycleVariationData = new LoanProductBorrowerCycleVariationData(id,cycleNumber, paramTypeData, conditionTypeData, defaultValue, minValue, maxValue);
return borrowerCycleVariationData;
}
}
@Override
public Collection<LoanProductData> retrieveAvailableLoanProductsForMix() {