MIFOSX-449 - Sync repayment dates with meeting dates for group and JLG loans
This commit is contained in:
+4
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `m_loan`
|
||||||
|
ADD COLUMN `sync_disbursement_with_meeting` TINYINT(1) NULL AFTER `loan_transaction_strategy_id`;
|
||||||
+4
@@ -9,6 +9,8 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
public interface CalendarInstanceRepository extends JpaRepository<CalendarInstance, Long>, JpaSpecificationExecutor<CalendarInstance> {
|
public interface CalendarInstanceRepository extends JpaRepository<CalendarInstance, Long>, JpaSpecificationExecutor<CalendarInstance> {
|
||||||
|
|
||||||
@@ -16,4 +18,6 @@ public interface CalendarInstanceRepository extends JpaRepository<CalendarInstan
|
|||||||
|
|
||||||
Collection<CalendarInstance> findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId);
|
Collection<CalendarInstance> findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId);
|
||||||
|
|
||||||
|
@Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId")
|
||||||
|
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 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.calendar.exception;
|
||||||
|
|
||||||
|
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link RuntimeException} thrown when Calendar resources are not found.
|
||||||
|
*/
|
||||||
|
public class MeetingFrequencyMismatchException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public MeetingFrequencyMismatchException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
|
||||||
|
super("error.msg.calendar." + postFix + ".not.the.same.as.meeting.frequency", defaultUserMessage, defaultUserMessageArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
+98
-2
@@ -27,6 +27,8 @@ import org.joda.time.format.DateTimeFormat;
|
|||||||
import org.joda.time.format.DateTimeFormatter;
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
import org.mifosplatform.infrastructure.core.service.DateUtils;
|
import org.mifosplatform.infrastructure.core.service.DateUtils;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||||
|
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||||
|
|
||||||
public class CalendarHelper {
|
public class CalendarHelper {
|
||||||
|
|
||||||
@@ -36,7 +38,20 @@ public class CalendarHelper {
|
|||||||
|
|
||||||
if (recur == null) { return null; }
|
if (recur == null) { return null; }
|
||||||
|
|
||||||
final Date nextRecDate = recur.getNextDate(new Date(seedDate.toDate()), new Date(startDate.toDate()));
|
final DateTime periodStart = new DateTime(startDate.toDate());
|
||||||
|
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");//Date format in iCal4J is hard coded
|
||||||
|
final String seedDateStr = df.format(seedDate.toDateTimeAtStartOfDay().toDate());
|
||||||
|
|
||||||
|
Date seed = null;
|
||||||
|
try {
|
||||||
|
seed = new Date(seedDateStr, "yyyy-MM-dd");
|
||||||
|
} catch (ParseException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final Date nextRecDate = recur.getNextDate(seed, periodStart);
|
||||||
return nextRecDate == null ? null : new LocalDate(nextRecDate);
|
return nextRecDate == null ? null : new LocalDate(nextRecDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +62,15 @@ public class CalendarHelper {
|
|||||||
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
|
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Collection<LocalDate> getRecurringDatesFrom(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) {
|
||||||
|
final LocalDate periodStartDate = (startDate == null) ? DateUtils.getLocalDateOfTenant() : startDate;
|
||||||
|
final LocalDate periodEndDate = DateUtils.getLocalDateOfTenant().plusYears(5);
|
||||||
|
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
|
||||||
|
}
|
||||||
|
|
||||||
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
|
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
|
||||||
final LocalDate periodStartDate, final LocalDate periodEndDate) {
|
final LocalDate periodStartDate, final LocalDate periodEndDate) {
|
||||||
final int maxCount = 15;// Default number of recurring dates
|
final int maxCount = 10;// Default number of recurring dates
|
||||||
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate, maxCount);
|
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate, maxCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,4 +230,79 @@ public class CalendarHelper {
|
|||||||
return DayNameEnum.MO;//Default it to Monday
|
return DayNameEnum.MO;//Default it to Monday
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule){
|
||||||
|
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||||
|
PeriodFrequencyType meetingFrequencyType = PeriodFrequencyType.INVALID;
|
||||||
|
if(recur.getFrequency().equals(Recur.DAILY)){
|
||||||
|
meetingFrequencyType = PeriodFrequencyType.DAYS;
|
||||||
|
}else if(recur.getFrequency().equals(Recur.WEEKLY)){
|
||||||
|
meetingFrequencyType = PeriodFrequencyType.WEEKS;
|
||||||
|
}else if(recur.getFrequency().equals(Recur.MONTHLY)){
|
||||||
|
meetingFrequencyType = PeriodFrequencyType.MONTHS;
|
||||||
|
}else if(recur.getFrequency().equals(Recur.YEARLY)){
|
||||||
|
meetingFrequencyType = PeriodFrequencyType.YEARS;
|
||||||
|
}
|
||||||
|
return meetingFrequencyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMeetingFrequencyFromPeriodFrequencyType(final PeriodFrequencyType periodFrequency){
|
||||||
|
String frequency = null;
|
||||||
|
if(periodFrequency.equals(PeriodFrequencyType.DAYS)){
|
||||||
|
frequency = Recur.DAILY;
|
||||||
|
}else if(periodFrequency.equals(PeriodFrequencyType.WEEKS)){
|
||||||
|
frequency = Recur.WEEKLY;
|
||||||
|
}else if(periodFrequency.equals(PeriodFrequencyType.MONTHS)){
|
||||||
|
frequency = Recur.MONTHLY;
|
||||||
|
}else if(periodFrequency.equals(PeriodFrequencyType.YEARS)){
|
||||||
|
frequency = Recur.YEARLY;
|
||||||
|
}
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getInterval(final String recurringRule){
|
||||||
|
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||||
|
return recur.getInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate, final Integer loanRepaymentInterval, final String frequency){
|
||||||
|
final Recur recur = CalendarHelper.getICalRecur(calendar.getRecurrence());
|
||||||
|
if (recur == null) { return null; }
|
||||||
|
LocalDate startDate = disbursementDate;
|
||||||
|
final LocalDate seedDate = calendar.getStartDateLocalDate();
|
||||||
|
if(isValidRedurringDate(calendar.getRecurrence(), seedDate, startDate)){
|
||||||
|
startDate = startDate.plusDays(1);
|
||||||
|
}
|
||||||
|
//Recurring dates should follow loanRepaymentInterval.
|
||||||
|
//e.g.
|
||||||
|
// for weekly meeting interval is 1
|
||||||
|
//where as for loan product with fortnightly frequency interval is 2
|
||||||
|
//to generate currect set of meeting dates reset interval same as loan repayment interval.
|
||||||
|
recur.setInterval(loanRepaymentInterval);
|
||||||
|
|
||||||
|
//Recurring dates should follow loanRepayment frequency.
|
||||||
|
//e.g.
|
||||||
|
// daily meeting frequency should support all loan products with any frequency type.
|
||||||
|
//to generate currect set of meeting dates reset frequency same as loan repayment frequency.
|
||||||
|
if(recur.getFrequency().equals(Recur.DAILY)){
|
||||||
|
recur.setFrequency(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DateTime periodStart = new DateTime(startDate.toDate());
|
||||||
|
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");//Date format in iCal4J is hard coded
|
||||||
|
final String seedDateStr = df.format(seedDate.toDateTimeAtStartOfDay().toDate());
|
||||||
|
|
||||||
|
Date seed = null;
|
||||||
|
try {
|
||||||
|
seed = new Date(seedDateStr, "yyyy-MM-dd");
|
||||||
|
} catch (ParseException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Date nextRecDate = recur.getNextDate(seed, periodStart);
|
||||||
|
final LocalDate firstRepaymentDate = nextRecDate == null ? null : new LocalDate(nextRecDate);
|
||||||
|
|
||||||
|
return firstRepaymentDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@ public class LoansApiResource {
|
|||||||
"summary", "repaymentSchedule", "transactions", "charges", "collateral", "guarantors", "meeting", "productOptions",
|
"summary", "repaymentSchedule", "transactions", "charges", "collateral", "guarantors", "meeting", "productOptions",
|
||||||
"amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions",
|
"amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions",
|
||||||
"termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions",
|
"termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions",
|
||||||
"loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions"));
|
"loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting"));
|
||||||
|
|
||||||
private final String resourceNameForPermissions = "LOAN";
|
private final String resourceNameForPermissions = "LOAN";
|
||||||
|
|
||||||
|
|||||||
+25
-12
@@ -87,6 +87,7 @@ public class LoanAccountData {
|
|||||||
private final Integer graceOnInterestCharged;
|
private final Integer graceOnInterestCharged;
|
||||||
private final LocalDate interestChargedFromDate;
|
private final LocalDate interestChargedFromDate;
|
||||||
private final LocalDate expectedFirstRepaymentOnDate;
|
private final LocalDate expectedFirstRepaymentOnDate;
|
||||||
|
private final Boolean syncDisbursementWithMeeting;
|
||||||
|
|
||||||
// timeline
|
// timeline
|
||||||
private final LoanApplicationTimelineData timeline;
|
private final LoanApplicationTimelineData timeline;
|
||||||
@@ -190,6 +191,7 @@ public class LoanAccountData {
|
|||||||
final Collection<StaffData> loanOfficerOptions = null;
|
final Collection<StaffData> loanOfficerOptions = null;
|
||||||
final Collection<CodeValueData> loanPurposeOptions = null;
|
final Collection<CodeValueData> loanPurposeOptions = null;
|
||||||
final Collection<CalendarData> calendarOptions = null;
|
final Collection<CalendarData> calendarOptions = null;
|
||||||
|
final Boolean syncDisbursementWithMeeting = null;
|
||||||
|
|
||||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||||
@@ -200,7 +202,8 @@ public class LoanAccountData {
|
|||||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
|
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
|
||||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||||
|
syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,6 +274,7 @@ public class LoanAccountData {
|
|||||||
final Collection<CodeValueData> loanPurposeOptions = null;
|
final Collection<CodeValueData> loanPurposeOptions = null;
|
||||||
final Collection<CodeValueData> loanCollateralOptions = null;
|
final Collection<CodeValueData> loanCollateralOptions = null;
|
||||||
final Collection<CalendarData> calendarOptions = null;
|
final Collection<CalendarData> calendarOptions = null;
|
||||||
|
final Boolean syncDisbursementWithMeeting = null;
|
||||||
|
|
||||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||||
@@ -281,7 +285,7 @@ public class LoanAccountData {
|
|||||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
|
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
|
||||||
@@ -298,7 +302,7 @@ public class LoanAccountData {
|
|||||||
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions);
|
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -370,6 +374,7 @@ public class LoanAccountData {
|
|||||||
final Collection<CodeValueData> loanPurposeOptions = null;
|
final Collection<CodeValueData> loanPurposeOptions = null;
|
||||||
final Collection<CodeValueData> loanCollateralOptions = null;
|
final Collection<CodeValueData> loanCollateralOptions = null;
|
||||||
final Collection<CalendarData> calendarOptions = null;
|
final Collection<CalendarData> calendarOptions = null;
|
||||||
|
final Boolean syncDisbursementWithMeeting = null;
|
||||||
|
|
||||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||||
@@ -380,7 +385,8 @@ public class LoanAccountData {
|
|||||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||||
|
syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
|
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
|
||||||
@@ -397,7 +403,8 @@ public class LoanAccountData {
|
|||||||
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions);
|
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
|
||||||
|
acc.syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
|
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
|
||||||
@@ -444,6 +451,7 @@ public class LoanAccountData {
|
|||||||
final LoanSummaryData summary = null;
|
final LoanSummaryData summary = null;
|
||||||
final BigDecimal feeChargesDueAtDisbursementCharged = null;
|
final BigDecimal feeChargesDueAtDisbursementCharged = null;
|
||||||
final ChargeData chargeTemplate = null;
|
final ChargeData chargeTemplate = null;
|
||||||
|
final Boolean syncDisbursementWithMeeting = null;
|
||||||
|
|
||||||
final LoanScheduleData repaymentSchedule = null;
|
final LoanScheduleData repaymentSchedule = null;
|
||||||
final Collection<LoanTransactionData> transactions = null;
|
final Collection<LoanTransactionData> transactions = null;
|
||||||
@@ -474,7 +482,7 @@ public class LoanAccountData {
|
|||||||
repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
|
repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
|
||||||
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
|
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
|
||||||
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
|
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
|
||||||
loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
|
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
|
||||||
@@ -520,7 +528,7 @@ public class LoanAccountData {
|
|||||||
acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors,
|
acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors,
|
||||||
calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -539,7 +547,7 @@ public class LoanAccountData {
|
|||||||
final EnumOptionData interestCalculationPeriodType, final LocalDate expectedFirstRepaymentOnDate,
|
final EnumOptionData interestCalculationPeriodType, final LocalDate expectedFirstRepaymentOnDate,
|
||||||
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
|
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
|
||||||
final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary,
|
final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary,
|
||||||
final BigDecimal feeChargesDueAtDisbursementCharged) {
|
final BigDecimal feeChargesDueAtDisbursementCharged, final Boolean syncDisbursementWithMeeting) {
|
||||||
|
|
||||||
final LoanScheduleData repaymentSchedule = null;
|
final LoanScheduleData repaymentSchedule = null;
|
||||||
final Collection<LoanTransactionData> transactions = null;
|
final Collection<LoanTransactionData> transactions = null;
|
||||||
@@ -572,7 +580,8 @@ public class LoanAccountData {
|
|||||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||||
|
syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -601,7 +610,8 @@ public class LoanAccountData {
|
|||||||
acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
|
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
|
||||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||||
|
acc.syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
|
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
|
||||||
@@ -626,7 +636,8 @@ public class LoanAccountData {
|
|||||||
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions);
|
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
|
||||||
|
acc.syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanAccountData(
|
private LoanAccountData(
|
||||||
@@ -672,7 +683,8 @@ public class LoanAccountData {
|
|||||||
final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
|
final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
|
||||||
final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions, final ChargeData chargeTemplate,
|
final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions, final ChargeData chargeTemplate,
|
||||||
final Collection<StaffData> loanOfficerOptions, final Collection<CodeValueData> loanPurposeOptions,
|
final Collection<StaffData> loanOfficerOptions, final Collection<CodeValueData> loanPurposeOptions,
|
||||||
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions) {
|
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
|
||||||
|
final Boolean syncDisbursementWithMeeting) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.accountNo = accountNo;
|
this.accountNo = accountNo;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
@@ -714,6 +726,7 @@ public class LoanAccountData {
|
|||||||
this.interestChargedFromDate = interestChargedFromDate;
|
this.interestChargedFromDate = interestChargedFromDate;
|
||||||
this.timeline = timeline;
|
this.timeline = timeline;
|
||||||
this.feeChargesAtDisbursementCharged = feeChargesDueAtDisbursementCharged;
|
this.feeChargesAtDisbursementCharged = feeChargesDueAtDisbursementCharged;
|
||||||
|
this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
|
||||||
|
|
||||||
// totals
|
// totals
|
||||||
this.summary = summary;
|
this.summary = summary;
|
||||||
|
|||||||
+36
-14
@@ -135,6 +135,9 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
@Column(name = "loan_status_id", nullable = false)
|
@Column(name = "loan_status_id", nullable = false)
|
||||||
private Integer loanStatus;
|
private Integer loanStatus;
|
||||||
|
|
||||||
|
@Column(name = "sync_disbursement_with_meeting", nullable = true)
|
||||||
|
private Boolean syncDisbursementWithMeeting;
|
||||||
|
|
||||||
// loan application states
|
// loan application states
|
||||||
@Temporal(TemporalType.DATE)
|
@Temporal(TemporalType.DATE)
|
||||||
@Column(name = "submittedon_date")
|
@Column(name = "submittedon_date")
|
||||||
@@ -276,31 +279,34 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
final Set<LoanCollateral> collateral) {
|
final Set<LoanCollateral> collateral) {
|
||||||
final LoanStatus status = null;
|
final LoanStatus status = null;
|
||||||
final Group group = null;
|
final Group group = null;
|
||||||
|
final Boolean syncDisbursementWithMeeting = null;
|
||||||
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||||
loanRepaymentScheduleDetail, status, loanCharges, collateral);
|
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType,
|
public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType,
|
||||||
final LoanProduct loanProduct, final Fund fund, final Staff officer,
|
final LoanProduct loanProduct, final Fund fund, final Staff officer,
|
||||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||||
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges) {
|
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
|
||||||
|
final Boolean syncDisbursementWithMeeting) {
|
||||||
final LoanStatus status = null;
|
final LoanStatus status = null;
|
||||||
final CodeValue loanPurpose = null;
|
final CodeValue loanPurpose = null;
|
||||||
final Set<LoanCollateral> collateral = null;
|
final Set<LoanCollateral> collateral = null;
|
||||||
final Client client = null;
|
final Client client = null;
|
||||||
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||||
loanRepaymentScheduleDetail, status, loanCharges, collateral);
|
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
|
public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
|
||||||
final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer,
|
final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer,
|
||||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||||
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges) {
|
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
|
||||||
|
final Boolean syncDisbursementWithMeeting) {
|
||||||
final LoanStatus status = null;
|
final LoanStatus status = null;
|
||||||
final CodeValue loanPurpose = null;
|
final CodeValue loanPurpose = null;
|
||||||
final Set<LoanCollateral> collateral = null;
|
final Set<LoanCollateral> collateral = null;
|
||||||
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||||
loanRepaymentScheduleDetail, status, loanCharges, collateral);
|
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Loan() {
|
protected Loan() {
|
||||||
@@ -310,7 +316,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund,
|
private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund,
|
||||||
final Staff loanOfficer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
final Staff loanOfficer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||||
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
|
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
|
||||||
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral) {
|
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting) {
|
||||||
|
|
||||||
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
|
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
|
||||||
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
|
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
|
||||||
@@ -348,6 +354,8 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
this.collateral = null;
|
this.collateral = null;
|
||||||
}
|
}
|
||||||
this.loanOfficerHistory = null;
|
this.loanOfficerHistory = null;
|
||||||
|
|
||||||
|
this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
|
private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
|
||||||
@@ -815,8 +823,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add clientId, groupId and loanType changes to actual changes
|
// add clientId, groupId and loanType changes to actual changes
|
||||||
// FIXME: AA - We may require separate api command to move loan from one
|
|
||||||
// client to another
|
|
||||||
final String clientIdParamName = "clientId";
|
final String clientIdParamName = "clientId";
|
||||||
final Long clientId = this.client == null ? null : this.client.getId();
|
final Long clientId = this.client == null ? null : this.client.getId();
|
||||||
if (command.isChangeInLongParameterNamed(clientIdParamName, clientId)) {
|
if (command.isChangeInLongParameterNamed(clientIdParamName, clientId)) {
|
||||||
@@ -916,6 +923,13 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String syncDisbursementParameterName = "syncDisbursementWithMeeting";
|
||||||
|
if (command.isChangeInBooleanParameterNamed(syncDisbursementParameterName, isSyncDisbursementWithMeeting())) {
|
||||||
|
final Boolean valueAsInput = command.booleanObjectValueOfParameterNamed(syncDisbursementParameterName);
|
||||||
|
actualChanges.put(syncDisbursementParameterName, valueAsInput);
|
||||||
|
this.syncDisbursementWithMeeting = valueAsInput;
|
||||||
|
}
|
||||||
|
|
||||||
final String interestChargedFromDateParamName = "interestChargedFromDate";
|
final String interestChargedFromDateParamName = "interestChargedFromDate";
|
||||||
if (command.isChangeInLocalDateParameterNamed(interestChargedFromDateParamName, getInterestChargedFromDate())) {
|
if (command.isChangeInLocalDateParameterNamed(interestChargedFromDateParamName, getInterestChargedFromDate())) {
|
||||||
final String valueAsInput = command.stringValueOfParameterNamed(interestChargedFromDateParamName);
|
final String valueAsInput = command.stringValueOfParameterNamed(interestChargedFromDateParamName);
|
||||||
@@ -1278,7 +1292,8 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
|
|
||||||
public Map<String, Object> disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
|
public Map<String, Object> disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
|
||||||
final JsonCommand command, final ApplicationCurrency currency, final List<Long> existingTransactionIds,
|
final JsonCommand command, final ApplicationCurrency currency, final List<Long> existingTransactionIds,
|
||||||
final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges, final PaymentDetail paymentDetail) {
|
final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges,
|
||||||
|
final PaymentDetail paymentDetail, final LocalDate firstRepaymentMeetingDate) {
|
||||||
|
|
||||||
updateLoanToPreDisbursalState();
|
updateLoanToPreDisbursalState();
|
||||||
|
|
||||||
@@ -1304,7 +1319,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
handleDisbursementTransaction(paymentDetail, actualDisbursementDate);
|
handleDisbursementTransaction(paymentDetail, actualDisbursementDate);
|
||||||
|
|
||||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
||||||
regenerateRepaymentSchedule(loanScheduleFactory, currency);
|
regenerateRepaymentSchedule(loanScheduleFactory, currency, firstRepaymentMeetingDate);
|
||||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||||
} else {
|
} else {
|
||||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||||
@@ -1331,7 +1346,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
* details/state.
|
* details/state.
|
||||||
*/
|
*/
|
||||||
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||||
final ApplicationCurrency applicationCurrency) {
|
final ApplicationCurrency applicationCurrency, final LocalDate firstRepaymentMeetingDate) {
|
||||||
|
|
||||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||||
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
||||||
@@ -1341,9 +1356,11 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
|
|
||||||
final Integer loanTermFrequency = this.termFrequency;
|
final Integer loanTermFrequency = this.termFrequency;
|
||||||
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
|
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
|
||||||
|
//If ExpectedFirstRepaymentOnDate is null and repayment is synced with meeting then use firstRepaymentMeetingDate
|
||||||
|
//to generate repayment schedule dates
|
||||||
|
final LocalDate firstRepaymentOnDate = (getExpectedFirstRepaymentOnDate() == null) ? firstRepaymentMeetingDate : getExpectedFirstRepaymentOnDate();
|
||||||
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
|
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
|
||||||
loanTermPeriodFrequencyType, getExpectedDisbursedOnLocalDate(), getExpectedFirstRepaymentOnDate(), getInArrearsTolerance(),
|
loanTermPeriodFrequencyType, getExpectedDisbursedOnLocalDate(), firstRepaymentOnDate, getInArrearsTolerance(),
|
||||||
this.loanRepaymentScheduleDetail);
|
this.loanRepaymentScheduleDetail);
|
||||||
|
|
||||||
LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges);
|
LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges);
|
||||||
@@ -2001,7 +2018,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
return disbursementDate;
|
return disbursementDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDate getExpectedFirstRepaymentOnDate() {
|
public LocalDate getExpectedFirstRepaymentOnDate() {
|
||||||
LocalDate firstRepaymentDate = null;
|
LocalDate firstRepaymentDate = null;
|
||||||
if (this.expectedFirstRepaymentOnDate != null) {
|
if (this.expectedFirstRepaymentOnDate != null) {
|
||||||
firstRepaymentDate = new LocalDate(this.expectedFirstRepaymentOnDate);
|
firstRepaymentDate = new LocalDate(this.expectedFirstRepaymentOnDate);
|
||||||
@@ -2299,4 +2316,9 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
this.loanSummaryWrapper = loanSummaryWrapper;
|
this.loanSummaryWrapper = loanSummaryWrapper;
|
||||||
this.transactionProcessorFactory = transactionProcessorFactory;
|
this.transactionProcessorFactory = transactionProcessorFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSyncDisbursementWithMeeting() {
|
||||||
|
return this.syncDisbursementWithMeeting == null ? false : this.syncDisbursementWithMeeting;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+82
-3
@@ -16,7 +16,13 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
|||||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
|
||||||
|
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
|
||||||
|
import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchException;
|
||||||
|
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||||
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||||
@@ -45,18 +51,20 @@ public class LoanScheduleAssembler {
|
|||||||
private final LoanChargeAssembler loanChargeAssembler;
|
private final LoanChargeAssembler loanChargeAssembler;
|
||||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||||
private final AprCalculator aprCalculator;
|
private final AprCalculator aprCalculator;
|
||||||
|
private final CalendarRepository calendarRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
|
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
|
||||||
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
|
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
|
||||||
final LoanScheduleGeneratorFactory loanScheduleFactory, final AprCalculator aprCalculator,
|
final LoanScheduleGeneratorFactory loanScheduleFactory, final AprCalculator aprCalculator,
|
||||||
final LoanChargeAssembler loanChargeAssembler) {
|
final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository) {
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||||
this.loanProductRepository = loanProductRepository;
|
this.loanProductRepository = loanProductRepository;
|
||||||
this.applicationCurrencyRepository = applicationCurrencyRepository;
|
this.applicationCurrencyRepository = applicationCurrencyRepository;
|
||||||
this.loanScheduleFactory = loanScheduleFactory;
|
this.loanScheduleFactory = loanScheduleFactory;
|
||||||
this.aprCalculator = aprCalculator;
|
this.aprCalculator = aprCalculator;
|
||||||
this.loanChargeAssembler = loanChargeAssembler;
|
this.loanChargeAssembler = loanChargeAssembler;
|
||||||
|
this.calendarRepository = calendarRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
|
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
|
||||||
@@ -106,8 +114,39 @@ public class LoanScheduleAssembler {
|
|||||||
final Money principalMoney = Money.of(currency, principal);
|
final Money principalMoney = Money.of(currency, principal);
|
||||||
|
|
||||||
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
|
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
|
||||||
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element);
|
LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element);
|
||||||
|
|
||||||
|
final Boolean synchDisbursement = fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
|
||||||
|
final Long calendarId = this.fromApiJsonHelper.extractLongNamed("calendarId", element);
|
||||||
|
Calendar calendar = null;
|
||||||
|
if ((synchDisbursement != null && synchDisbursement.booleanValue()) ||
|
||||||
|
(calendarId != null && calendarId != 0)) {
|
||||||
|
calendar = this.calendarRepository.findOne(calendarId);
|
||||||
|
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
|
||||||
|
|
||||||
|
//validate repayment frequency and interval with meeting frequency and interval
|
||||||
|
PeriodFrequencyType meetingPeriodFrequency = CalendarHelper.getMeetingPeriodFrequencyType(calendar.getRecurrence());
|
||||||
|
validateRepaymentFrequencyIsSameAsMeetingFrequency(meetingPeriodFrequency.getValue(), repaymentFrequencyType,
|
||||||
|
CalendarHelper.getInterval(calendar.getRecurrence()),repaymentEvery);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (synchDisbursement != null && synchDisbursement.booleanValue()) {
|
||||||
|
validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar);
|
||||||
|
}
|
||||||
|
|
||||||
|
//if calendar is attached (not null) then reschedule repayment dates according to meeting
|
||||||
|
if (null != calendar) {
|
||||||
|
//TODO : AA - Is it require to reset repaymentsStartingFromDate or set only if it is not provided (or null)
|
||||||
|
// Currently provided repaymentsStartingFromDate takes precedence over system generated next meeting date
|
||||||
|
if(repaymentsStartingFromDate == null){
|
||||||
|
//FIXME: AA - Possibility of having next meeting date immediately after disbursement date,
|
||||||
|
//need to have minimum number of days gap between disbursement and first repayment date.
|
||||||
|
final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
|
||||||
|
repaymentsStartingFromDate = CalendarHelper.getFirstRepaymentMeetingDate(calendar, expectedDisbursementDate, repaymentEvery, frequency);
|
||||||
|
}else{//validate user provided repaymentsStartFromDate
|
||||||
|
validateRepaymentsStartDateWithMeetingDates(repaymentsStartingFromDate, calendar);
|
||||||
|
}
|
||||||
|
}
|
||||||
// grace details
|
// grace details
|
||||||
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
||||||
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
||||||
@@ -125,7 +164,47 @@ public class LoanScheduleAssembler {
|
|||||||
graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney);
|
graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) {
|
private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) {
|
||||||
|
if(repaymentsStartingFromDate != null && !CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentsStartingFromDate)){
|
||||||
|
final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date";
|
||||||
|
throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage, repaymentsStartingFromDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateDisbursementDateWithMeetingDates(
|
||||||
|
final LocalDate expectedDisbursementDate, final Calendar calendar) {
|
||||||
|
// disbursement date should fall on a meeting date
|
||||||
|
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(),
|
||||||
|
calendar.getStartDateLocalDate(), expectedDisbursementDate)) {
|
||||||
|
final String errorMessage = "Expected disbursement date '"
|
||||||
|
+ expectedDisbursementDate
|
||||||
|
+ "' do not fall on a meeting date";
|
||||||
|
throw new LoanApplicationDateException(
|
||||||
|
"disbursement.date.do.not.match.meeting.date",
|
||||||
|
errorMessage, expectedDisbursementDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRepaymentFrequencyIsSameAsMeetingFrequency(
|
||||||
|
final Integer meetingFrequency, final Integer repaymentFrequency,
|
||||||
|
final Integer meetingInterval, final Integer repaymentInterval) {
|
||||||
|
//meeting with daily frequency should allow loan products with any frequency.
|
||||||
|
if(!PeriodFrequencyType.DAYS.getValue().equals(meetingFrequency)){
|
||||||
|
//repayment frequency must match with meeting frequency
|
||||||
|
if(!meetingFrequency.equals(repaymentFrequency)){
|
||||||
|
throw new MeetingFrequencyMismatchException("loanapplication.repayment.frequency", "Loan repayment frequency period must match that of meeting frequency period", repaymentFrequency);
|
||||||
|
}else if(meetingFrequency.equals(repaymentFrequency)){//repayment frequency is same as meeting frequency
|
||||||
|
//repayment interval should be same or multiple of meeting interval
|
||||||
|
if (repaymentInterval % meetingInterval != 0) {
|
||||||
|
// throw exception: Loan product frequency/interval
|
||||||
|
throw new MeetingFrequencyMismatchException("loanapplication.repayment.interval", "Loan repayment repaid every # must equal or multiple of meeting interval", repaymentInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) {
|
||||||
LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
|
LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
|
||||||
return loanApplicationTerms.toLoanProductRelatedDetail();
|
return loanApplicationTerms.toLoanProductRelatedDetail();
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-1
@@ -42,7 +42,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
|
|||||||
"loanTermFrequencyType", "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType",
|
"loanTermFrequencyType", "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType",
|
||||||
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment",
|
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment",
|
||||||
"graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale",
|
"graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale",
|
||||||
"dateFormat", "charges", "collateral"));
|
"dateFormat", "charges", "collateral", "syncDisbursementWithMeeting"));
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
|
||||||
@@ -180,6 +180,25 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean meetingIdRequired = false;
|
||||||
|
//validate syncDisbursement
|
||||||
|
final String syncDisbursementParameterName = "syncDisbursementWithMeeting";
|
||||||
|
if (fromApiJsonHelper.parameterExists(syncDisbursementParameterName, element)) {
|
||||||
|
final Boolean syncDisbursement = fromApiJsonHelper.extractBooleanNamed(syncDisbursementParameterName, element);
|
||||||
|
if(syncDisbursement == null){
|
||||||
|
baseDataValidator.reset().parameter(syncDisbursementParameterName).value(syncDisbursement).trueOrFalseRequired(false);
|
||||||
|
}else if(syncDisbursement.booleanValue()) {
|
||||||
|
meetingIdRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String calendarIdParameterName = "calendarId";
|
||||||
|
//if disbursement is synced then must have a meeting (calendar)
|
||||||
|
if (meetingIdRequired || fromApiJsonHelper.parameterExists(syncDisbursementParameterName, element)) {
|
||||||
|
final Long calendarId = fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
|
||||||
|
baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).notNull().integerGreaterThanZero();
|
||||||
|
}
|
||||||
|
|
||||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||||
"Validation errors exist.", dataValidationErrors); }
|
"Validation errors exist.", dataValidationErrors); }
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-13
@@ -47,7 +47,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
|||||||
"accountNo", "externalId", "fundId", "loanOfficerId", // optional
|
"accountNo", "externalId", "fundId", "loanOfficerId", // optional
|
||||||
"loanPurposeId", "inArrearsTolerance", "charges", "collateral", // optional
|
"loanPurposeId", "inArrearsTolerance", "charges", "collateral", // optional
|
||||||
"transactionProcessingStrategyId", // settings
|
"transactionProcessingStrategyId", // settings
|
||||||
"calendarId" // optional
|
"calendarId", // optional
|
||||||
|
"syncDisbursementWithMeeting"//optional
|
||||||
));
|
));
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
@@ -230,12 +231,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
|||||||
baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId).notNull()
|
baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId).notNull()
|
||||||
.integerGreaterThanZero();
|
.integerGreaterThanZero();
|
||||||
|
|
||||||
final String calendarIdParameterName = "calendarId";
|
|
||||||
if (fromApiJsonHelper.parameterExists(calendarIdParameterName, element)) {
|
|
||||||
final Long calendarId = fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
|
|
||||||
baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).ignoreIfNull().integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
|
|
||||||
// charges
|
// charges
|
||||||
final String chargesParameterName = "charges";
|
final String chargesParameterName = "charges";
|
||||||
if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
|
if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
|
||||||
@@ -302,6 +297,25 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean meetingIdRequired = false;
|
||||||
|
//validate syncDisbursement
|
||||||
|
final String syncDisbursementParameterName = "syncDisbursementWithMeeting";
|
||||||
|
if (fromApiJsonHelper.parameterExists(syncDisbursementParameterName, element)) {
|
||||||
|
final Boolean syncDisbursement = fromApiJsonHelper.extractBooleanNamed(syncDisbursementParameterName, element);
|
||||||
|
if(syncDisbursement == null){
|
||||||
|
baseDataValidator.reset().parameter(syncDisbursementParameterName).value(syncDisbursement).trueOrFalseRequired(false);
|
||||||
|
}else if(syncDisbursement.booleanValue()) {
|
||||||
|
meetingIdRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String calendarIdParameterName = "calendarId";
|
||||||
|
//if disbursement is synced then must have a meeting (calendar)
|
||||||
|
if (meetingIdRequired || fromApiJsonHelper.parameterExists(syncDisbursementParameterName, element)) {
|
||||||
|
final Long calendarId = fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
|
||||||
|
baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).notNull().integerGreaterThanZero();
|
||||||
|
}
|
||||||
|
|
||||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,12 +531,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
|||||||
.notExceedingLengthOf(500);
|
.notExceedingLengthOf(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String calendarIdParameterName = "calendarId";
|
|
||||||
if (fromApiJsonHelper.parameterExists(calendarIdParameterName, element)) {
|
|
||||||
final Long calendarId = fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
|
|
||||||
baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).ignoreIfNull().integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
|
|
||||||
// charges
|
// charges
|
||||||
final String chargesParameterName = "charges";
|
final String chargesParameterName = "charges";
|
||||||
if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
|
if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
|
||||||
@@ -591,6 +599,25 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean meetingIdRequired = false;
|
||||||
|
//validate syncDisbursement
|
||||||
|
final String syncDisbursementParameterName = "syncDisbursementWithMeeting";
|
||||||
|
if (fromApiJsonHelper.parameterExists(syncDisbursementParameterName, element)) {
|
||||||
|
final Boolean syncDisbursement = fromApiJsonHelper.extractBooleanNamed(syncDisbursementParameterName, element);
|
||||||
|
if(syncDisbursement == null){
|
||||||
|
baseDataValidator.reset().parameter(syncDisbursementParameterName).value(syncDisbursement).trueOrFalseRequired(false);
|
||||||
|
}else if(syncDisbursement.booleanValue()) {
|
||||||
|
meetingIdRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String calendarIdParameterName = "calendarId";
|
||||||
|
//if disbursement is synced then must have a meeting (calendar)
|
||||||
|
if (meetingIdRequired || fromApiJsonHelper.parameterExists(syncDisbursementParameterName, element)) {
|
||||||
|
final Long calendarId = fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
|
||||||
|
baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).notNull().integerGreaterThanZero();
|
||||||
|
}
|
||||||
|
|
||||||
if (!atLeastOneParameterPassedForUpdate) {
|
if (!atLeastOneParameterPassedForUpdate) {
|
||||||
final Object forceError = null;
|
final Object forceError = null;
|
||||||
baseDataValidator.reset().anyOfNotNull(forceError);
|
baseDataValidator.reset().anyOfNotNull(forceError);
|
||||||
@@ -639,4 +666,5 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
|||||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||||
"Validation errors exist.", dataValidationErrors); }
|
"Validation errors exist.", dataValidationErrors); }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+37
@@ -21,6 +21,10 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
|
|||||||
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
|
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
|
||||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||||
|
import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException;
|
||||||
|
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -67,6 +71,23 @@ public final class LoanEventApiJsonValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance) {
|
||||||
|
if (null != calendarInstance) {
|
||||||
|
Calendar calendar = calendarInstance.getCalendar();
|
||||||
|
if (calendar != null && actualDisbursementDate != null) {
|
||||||
|
// Disbursement date should fall on a meeting date
|
||||||
|
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), actualDisbursementDate)) {
|
||||||
|
final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString()
|
||||||
|
+ "' does not fall on a meeting date.";
|
||||||
|
throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage, actualDisbursementDate.toString(),
|
||||||
|
calendar.getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void validateTransaction(final String json) {
|
public void validateTransaction(final String json) {
|
||||||
|
|
||||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||||
@@ -121,6 +142,22 @@ public final class LoanEventApiJsonValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void validateRepaymentDateWithMeetingDate(LocalDate repaymentDate, final CalendarInstance calendarInstance) {
|
||||||
|
if (null != calendarInstance) {
|
||||||
|
Calendar calendar = calendarInstance.getCalendar();
|
||||||
|
if (calendar != null && repaymentDate != null) {
|
||||||
|
// Disbursement date should fall on a meeting date
|
||||||
|
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
|
||||||
|
final String errorMessage = "Transaction date '" + repaymentDate.toString()
|
||||||
|
+ "' does not fall on a meeting date.";
|
||||||
|
throw new NotValidRecurringDateException("loan.transaction.date", errorMessage, repaymentDate.toString(),
|
||||||
|
calendar.getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||||
// Validate all string payment detail fields for max length
|
// Validate all string payment detail fields for max length
|
||||||
final Integer paymentTypeId = fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
|
final Integer paymentTypeId = fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
|
||||||
|
|||||||
+15
-68
@@ -12,7 +12,6 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.joda.time.LocalDate;
|
|
||||||
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
||||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
import org.mifosplatform.infrastructure.core.api.JsonQuery;
|
import org.mifosplatform.infrastructure.core.api.JsonQuery;
|
||||||
@@ -31,8 +30,6 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
|||||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
|
||||||
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
|
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
|
||||||
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
|
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
|
||||||
import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException;
|
|
||||||
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
|
||||||
import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator;
|
import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator;
|
||||||
import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory;
|
import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory;
|
||||||
import org.mifosplatform.portfolio.client.domain.Client;
|
import org.mifosplatform.portfolio.client.domain.Client;
|
||||||
@@ -48,7 +45,6 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTrans
|
|||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
|
||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
|
||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
|
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||||
@@ -156,16 +152,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
|||||||
|
|
||||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||||
|
|
||||||
// validate dates against calendar recurring dates
|
|
||||||
// get calendar
|
|
||||||
final Long calendarId = command.longValueOfParameterNamed("calendarId");
|
|
||||||
Calendar calendar = null;
|
|
||||||
if (calendarId != null && calendarId != 0) {
|
|
||||||
calendar = this.calendarRepository.findOne(calendarId);
|
|
||||||
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
|
|
||||||
validateCalendarDates(command, calendar);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Loan newLoanApplication = loanAssembler.assembleFrom(command, currentUser);
|
final Loan newLoanApplication = loanAssembler.assembleFrom(command, currentUser);
|
||||||
|
|
||||||
this.loanRepository.save(newLoanApplication);
|
this.loanRepository.save(newLoanApplication);
|
||||||
@@ -184,12 +170,19 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save calendar instance
|
// Save calendar instance
|
||||||
if (calendar != null) {
|
final Long calendarId = command.longValueOfParameterNamed("calendarId");
|
||||||
|
Calendar calendar = null;
|
||||||
|
|
||||||
|
if (calendarId != null && calendarId != 0) {
|
||||||
|
calendar = this.calendarRepository.findOne(calendarId);
|
||||||
|
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
|
||||||
|
|
||||||
CalendarInstance calendarInstance = new CalendarInstance(calendar, newLoanApplication.getId(),
|
CalendarInstance calendarInstance = new CalendarInstance(calendar, newLoanApplication.getId(),
|
||||||
CalendarEntityType.LOANS.getValue());
|
CalendarEntityType.LOANS.getValue());
|
||||||
this.calendarInstanceRepository.save(calendarInstance);
|
this.calendarInstanceRepository.save(calendarInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder() //
|
||||||
.withCommandId(command.commandId()) //
|
.withCommandId(command.commandId()) //
|
||||||
.withEntityId(newLoanApplication.getId()) //
|
.withEntityId(newLoanApplication.getId()) //
|
||||||
@@ -209,14 +202,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
|||||||
|
|
||||||
this.fromApiJsonDeserializer.validateForModify(command.json());
|
this.fromApiJsonDeserializer.validateForModify(command.json());
|
||||||
|
|
||||||
final Long calendarId = command.longValueOfParameterNamed("calendarId");
|
|
||||||
Calendar calendar = null;
|
|
||||||
if (calendarId != null && calendarId != 0) {
|
|
||||||
calendar = this.calendarRepository.findOne(calendarId);
|
|
||||||
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
|
|
||||||
validateCalendarDates(command, calendar);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Loan existingLoanApplication = retrieveLoanBy(loanId);
|
final Loan existingLoanApplication = retrieveLoanBy(loanId);
|
||||||
|
|
||||||
if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(
|
if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(
|
||||||
@@ -309,6 +294,13 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
|||||||
this.noteRepository.save(note);
|
this.noteRepository.save(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Long calendarId = command.longValueOfParameterNamed("calendarId");
|
||||||
|
Calendar calendar = null;
|
||||||
|
if (calendarId != null && calendarId != 0) {
|
||||||
|
calendar = this.calendarRepository.findOne(calendarId);
|
||||||
|
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
|
||||||
|
}
|
||||||
|
|
||||||
List<CalendarInstance> ciList = (List<CalendarInstance>) this.calendarInstanceRepository.findByEntityIdAndEntityTypeId(loanId,
|
List<CalendarInstance> ciList = (List<CalendarInstance>) this.calendarInstanceRepository.findByEntityIdAndEntityTypeId(loanId,
|
||||||
CalendarEntityType.LOANS.getValue());
|
CalendarEntityType.LOANS.getValue());
|
||||||
if (calendar != null) {
|
if (calendar != null) {
|
||||||
@@ -534,49 +526,4 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
|||||||
return loan;
|
return loan;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateCalendarDates(final JsonCommand command, final Calendar calendar) {
|
|
||||||
|
|
||||||
final LocalDate edDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");
|
|
||||||
|
|
||||||
if (edDate != null) {
|
|
||||||
// Expected disbursement date should not be before Meeting start
|
|
||||||
// date
|
|
||||||
if (edDate.isBefore(calendar.getStartDateLocalDate())) {
|
|
||||||
final String errorMessage = "Expected disbursement date '" + edDate.toString() + "' cannot be before meeting start date '"
|
|
||||||
+ calendar.getStartDate().toString() + "' ";
|
|
||||||
throw new LoanApplicationDateException("disbursement.date.cannot.be.before.meeting.start.date", errorMessage, edDate,
|
|
||||||
calendar.getStartDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disbursement date should fall on a meeting date
|
|
||||||
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), edDate)) {
|
|
||||||
final String errorMessage = "Expected disbursement date '" + edDate.toString() + "' does not fall on a meeting date.";
|
|
||||||
throw new NotValidRecurringDateException("loan.expected.disbursement.date", errorMessage, edDate.toString(),
|
|
||||||
calendar.getTitle());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
final LocalDate repStartingDate = command.localDateValueOfParameterNamed("repaymentsStartingFromDate");
|
|
||||||
|
|
||||||
if (repStartingDate != null) {
|
|
||||||
|
|
||||||
// First repayment date should not be before Meeting date
|
|
||||||
if (repStartingDate.isBefore(calendar.getStartDateLocalDate())) {
|
|
||||||
final String errorMessage = "First repayment date '" + repStartingDate.toString()
|
|
||||||
+ "' cannot be before meeting start date '" + calendar.getStartDate().toString() + "' ";
|
|
||||||
throw new LoanApplicationDateException("first.repayment.date.cannot.be.before.meeting.start.date", errorMessage,
|
|
||||||
repStartingDate, calendar.getStartDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repStartingDate)) {
|
|
||||||
final String errorMessage = "The First repayment date '" + repStartingDate.toString()
|
|
||||||
+ "' does not fall on a meeting date.";
|
|
||||||
throw new NotValidRecurringDateException("loan.first.repayment.date", errorMessage, repStartingDate.toString(),
|
|
||||||
calendar.getTitle());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+3
-2
@@ -112,6 +112,7 @@ public class LoanAssembler {
|
|||||||
final Long loanOfficerId = fromApiJsonHelper.extractLongNamed("loanOfficerId", element);
|
final Long loanOfficerId = fromApiJsonHelper.extractLongNamed("loanOfficerId", element);
|
||||||
final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element);
|
final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element);
|
||||||
final Long loanPurposeId = fromApiJsonHelper.extractLongNamed("loanPurposeId", element);
|
final Long loanPurposeId = fromApiJsonHelper.extractLongNamed("loanPurposeId", element);
|
||||||
|
final Boolean syncDisbursementWithMeeting = fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
|
||||||
|
|
||||||
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
|
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
|
||||||
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
|
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
|
||||||
@@ -154,7 +155,7 @@ public class LoanAssembler {
|
|||||||
if (group == null) { throw new GroupNotFoundException(groupId); }
|
if (group == null) { throw new GroupNotFoundException(groupId); }
|
||||||
|
|
||||||
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
|
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
|
||||||
loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges);
|
loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client != null && group != null) {
|
if (client != null && group != null) {
|
||||||
@@ -162,7 +163,7 @@ public class LoanAssembler {
|
|||||||
if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); }
|
if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); }
|
||||||
|
|
||||||
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(),
|
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(),
|
||||||
loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges);
|
loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element);
|
final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element);
|
||||||
|
|||||||
+6
-2
@@ -442,7 +442,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
+ " la.fee_charges_overdue_derived as feeChargesOverdue,"
|
+ " la.fee_charges_overdue_derived as feeChargesOverdue,"
|
||||||
+ " la.penalty_charges_overdue_derived as penaltyChargesOverdue,"
|
+ " la.penalty_charges_overdue_derived as penaltyChargesOverdue,"
|
||||||
+ " la.total_overdue_derived as totalOverdue,"
|
+ " la.total_overdue_derived as totalOverdue,"
|
||||||
+ " la.overdue_since_date_derived as overdueSinceDate"
|
+ " la.overdue_since_date_derived as overdueSinceDate,"
|
||||||
|
+ " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting"
|
||||||
+ " from m_loan l" //
|
+ " from m_loan l" //
|
||||||
+ " join m_product_loan lp on lp.id = l.product_id" //
|
+ " join m_product_loan lp on lp.id = l.product_id" //
|
||||||
+ " join m_currency rc on rc.`code` = l.currency_code" //
|
+ " join m_currency rc on rc.`code` = l.currency_code" //
|
||||||
@@ -584,6 +585,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
final LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstRepaymentOnDate");
|
final LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstRepaymentOnDate");
|
||||||
final LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate");
|
final LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate");
|
||||||
|
|
||||||
|
final Boolean syncDisbursementWithMeeting = rs.getBoolean("syncDisbursementWithMeeting");
|
||||||
|
|
||||||
final BigDecimal feeChargesDueAtDisbursementCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
|
final BigDecimal feeChargesDueAtDisbursementCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
|
||||||
"feeChargesDueAtDisbursementCharged");
|
"feeChargesDueAtDisbursementCharged");
|
||||||
LoanSummaryData loanSummary = null;
|
LoanSummaryData loanSummary = null;
|
||||||
@@ -652,7 +655,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId,
|
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId,
|
||||||
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
|
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
|
||||||
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||||
graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged);
|
graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged,
|
||||||
|
syncDisbursementWithMeeting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+57
-38
@@ -8,7 +8,6 @@ package org.mifosplatform.portfolio.loanaccount.service;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -29,7 +28,6 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
|||||||
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
|
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
|
||||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
|
||||||
import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException;
|
|
||||||
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
||||||
import org.mifosplatform.portfolio.charge.domain.Charge;
|
import org.mifosplatform.portfolio.charge.domain.Charge;
|
||||||
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
|
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||||
@@ -57,7 +55,6 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
|
|||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
|
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||||
@@ -65,6 +62,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFound
|
|||||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||||
import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
|
import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
|
||||||
import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
|
import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
|
||||||
|
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||||
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
|
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
|
||||||
import org.mifosplatform.portfolio.note.domain.Note;
|
import org.mifosplatform.portfolio.note.domain.Note;
|
||||||
import org.mifosplatform.portfolio.note.domain.NoteRepository;
|
import org.mifosplatform.portfolio.note.domain.NoteRepository;
|
||||||
@@ -74,7 +72,6 @@ import org.mifosplatform.useradministration.domain.AppUser;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
||||||
@@ -139,11 +136,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
this.loanEventApiJsonValidator.validateDisbursement(command.json());
|
this.loanEventApiJsonValidator.validateDisbursement(command.json());
|
||||||
|
|
||||||
final Loan loan = retrieveLoanBy(loanId);
|
final Loan loan = retrieveLoanBy(loanId);
|
||||||
|
// validate actual disbursement date against meeting date
|
||||||
// validate actual disbursement date against meeting date
|
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(),
|
||||||
Collection<CalendarInstance> calendarInstances = this.calendarInstanceRepository.findByEntityIdAndEntityTypeId(loan.getId(),
|
|
||||||
CalendarEntityType.LOANS.getValue());
|
CalendarEntityType.LOANS.getValue());
|
||||||
validateDisbursementDate(command.localDateValueOfParameterNamed("actualDisbursementDate"), calendarInstances);
|
if(loan.isSyncDisbursementWithMeeting()){
|
||||||
|
|
||||||
|
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||||
|
this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance);
|
||||||
|
}
|
||||||
|
|
||||||
final MonetaryCurrency currency = loan.getCurrency();
|
final MonetaryCurrency currency = loan.getCurrency();
|
||||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||||
@@ -155,8 +155,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||||
|
|
||||||
|
//Recalculate first repayment date based in actual disbursement date.
|
||||||
|
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||||
|
final LocalDate firstRepaymentMeetingDate = getFirstRepaymentMeetingDate(actualDisbursementDate, loan, calendarInstance);
|
||||||
|
|
||||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||||
existingReversedTransactionIds, changes, paymentDetail);
|
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentMeetingDate);
|
||||||
|
|
||||||
if (!changes.isEmpty()) {
|
if (!changes.isEmpty()) {
|
||||||
this.loanRepository.save(loan);
|
this.loanRepository.save(loan);
|
||||||
@@ -181,35 +185,38 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateDisbursementDate(final LocalDate disbursementDate, final Collection<CalendarInstance> calendarInstances) {
|
private LocalDate getFirstRepaymentMeetingDate(
|
||||||
if (!CollectionUtils.isEmpty(calendarInstances)) {
|
final LocalDate actualDisbursementDate, final Loan loan,
|
||||||
Calendar calendar = null;
|
final CalendarInstance calendarInstance) {
|
||||||
for (CalendarInstance calendarInstance : calendarInstances) {
|
final Calendar calendar = (calendarInstance == null) ? null
|
||||||
calendar = calendarInstance.getCalendar();
|
: calendarInstance.getCalendar();
|
||||||
break;
|
LocalDate firstRepaymentMeetingDate = null;
|
||||||
}
|
if (calendar != null) {// sync repayments
|
||||||
if (calendar != null && disbursementDate != null) {
|
|
||||||
|
|
||||||
// Actual disbursement date cannot be before Meeting start
|
// TODO: AA - user provided first repayment date takes precedence over recalculated meeting date
|
||||||
// date
|
if (loan.getExpectedFirstRepaymentOnDate() == null) {
|
||||||
if (disbursementDate.isBefore(calendar.getStartDateLocalDate())) {
|
// FIXME: AA - Possibility of having next meeting date
|
||||||
final String errorMessage = "Actual disbursement date '" + disbursementDate.toString()
|
// immediately after disbursement date,
|
||||||
+ "' cannot be before meeting start date '" + calendar.getStartDate().toString() + "' ";
|
// need to have minimum number of days gap between disbursement
|
||||||
throw new LoanApplicationDateException("actual.disbursement.date.cannot.be.before.meeting.start.date", errorMessage,
|
// and first repayment date.
|
||||||
disbursementDate.toString(), calendar.getStartDate());
|
final LoanProductRelatedDetail repaymentScheduleDetails = loan
|
||||||
}
|
.repaymentScheduleDetail();
|
||||||
|
if (repaymentScheduleDetails != null) {// Not expecting to be
|
||||||
// Disbursement date should fall on a meeting date
|
// null
|
||||||
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), disbursementDate)) {
|
final Integer repayEvery = repaymentScheduleDetails
|
||||||
final String errorMessage = "Expected disbursement date '" + disbursementDate.toString()
|
.getRepayEvery();
|
||||||
+ "' does not fall on a meeting date.";
|
final String frequency = CalendarHelper
|
||||||
throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage, disbursementDate.toString(),
|
.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
|
||||||
calendar.getTitle());
|
.getRepaymentPeriodFrequencyType());
|
||||||
}
|
firstRepaymentMeetingDate = CalendarHelper
|
||||||
|
.getFirstRepaymentMeetingDate(calendar,
|
||||||
}
|
actualDisbursementDate, repayEvery,
|
||||||
}
|
frequency);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstRepaymentMeetingDate;
|
||||||
|
}
|
||||||
|
|
||||||
/****
|
/****
|
||||||
* TODO Vishwas: Pair with Ashok and re-factor collection sheet code-base
|
* TODO Vishwas: Pair with Ashok and re-factor collection sheet code-base
|
||||||
@@ -235,8 +242,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||||
|
|
||||||
|
// Bulk disbursement should happen on meeting date (mostly from collection sheet).
|
||||||
|
//FIXME: AA - this should be first meeting date based on disbursement date and next available meeting dates
|
||||||
|
//assuming repayment schedule won't regenerate because expected disbursement and actual disbursement happens on same date
|
||||||
|
final LocalDate firstRepaymentOnDate = null;
|
||||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||||
existingReversedTransactionIds, changes, paymentDetail);
|
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentOnDate);
|
||||||
|
|
||||||
if (!changes.isEmpty()) {
|
if (!changes.isEmpty()) {
|
||||||
this.loanRepository.save(loan);
|
this.loanRepository.save(loan);
|
||||||
@@ -328,6 +339,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final BigDecimal transactionAmount, final LocalDate transactionDate, final String noteText) {
|
final BigDecimal transactionAmount, final LocalDate transactionDate, final String noteText) {
|
||||||
final Loan loan = retrieveLoanBy(loanId);
|
final Loan loan = retrieveLoanBy(loanId);
|
||||||
|
|
||||||
|
//TODO: Is it required to validate transaction date with meeting dates if repayments is synced with meeting?
|
||||||
|
/*if(loan.isSyncDisbursementWithMeeting()){
|
||||||
|
// validate actual disbursement date against meeting date
|
||||||
|
CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(),
|
||||||
|
CalendarEntityType.LOANS.getValue());
|
||||||
|
this.loanEventApiJsonValidator.validateRepaymentDateWithMeetingDate(transactionDate, calendarInstance);
|
||||||
|
}*/
|
||||||
|
|
||||||
final List<Long> existingTransactionIds = new ArrayList<Long>();
|
final List<Long> existingTransactionIds = new ArrayList<Long>();
|
||||||
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user