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.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
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);
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
+99
-3
@@ -27,6 +27,8 @@ import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.mifosplatform.infrastructure.core.service.DateUtils;
|
||||
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public class CalendarHelper {
|
||||
|
||||
@@ -35,8 +37,21 @@ public class CalendarHelper {
|
||||
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -46,10 +61,16 @@ public class CalendarHelper {
|
||||
final LocalDate periodEndDate = (endDate == null) ? DateUtils.getLocalDateOfTenant().plusYears(5) : endDate;
|
||||
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,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -209,4 +230,79 @@ public class CalendarHelper {
|
||||
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",
|
||||
"amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions",
|
||||
"termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions",
|
||||
"loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions"));
|
||||
"loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting"));
|
||||
|
||||
private final String resourceNameForPermissions = "LOAN";
|
||||
|
||||
|
||||
+26
-13
@@ -87,6 +87,7 @@ public class LoanAccountData {
|
||||
private final Integer graceOnInterestCharged;
|
||||
private final LocalDate interestChargedFromDate;
|
||||
private final LocalDate expectedFirstRepaymentOnDate;
|
||||
private final Boolean syncDisbursementWithMeeting;
|
||||
|
||||
// timeline
|
||||
private final LoanApplicationTimelineData timeline;
|
||||
@@ -190,6 +191,7 @@ public class LoanAccountData {
|
||||
final Collection<StaffData> loanOfficerOptions = null;
|
||||
final Collection<CodeValueData> loanPurposeOptions = null;
|
||||
final Collection<CalendarData> calendarOptions = null;
|
||||
final Boolean syncDisbursementWithMeeting = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
@@ -200,7 +202,8 @@ public class LoanAccountData {
|
||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
|
||||
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> loanCollateralOptions = null;
|
||||
final Collection<CalendarData> calendarOptions = null;
|
||||
final Boolean syncDisbursementWithMeeting = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
@@ -281,7 +285,7 @@ public class LoanAccountData {
|
||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
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) {
|
||||
@@ -298,7 +302,7 @@ public class LoanAccountData {
|
||||
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||
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,7 +374,8 @@ public class LoanAccountData {
|
||||
final Collection<CodeValueData> loanPurposeOptions = null;
|
||||
final Collection<CodeValueData> loanCollateralOptions = null;
|
||||
final Collection<CalendarData> calendarOptions = null;
|
||||
|
||||
final Boolean syncDisbursementWithMeeting = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
currencyData, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
|
||||
@@ -380,7 +385,8 @@ public class LoanAccountData {
|
||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
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) {
|
||||
@@ -397,7 +403,8 @@ public class LoanAccountData {
|
||||
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||
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,
|
||||
@@ -444,6 +451,7 @@ public class LoanAccountData {
|
||||
final LoanSummaryData summary = null;
|
||||
final BigDecimal feeChargesDueAtDisbursementCharged = null;
|
||||
final ChargeData chargeTemplate = null;
|
||||
final Boolean syncDisbursementWithMeeting = null;
|
||||
|
||||
final LoanScheduleData repaymentSchedule = null;
|
||||
final Collection<LoanTransactionData> transactions = null;
|
||||
@@ -474,7 +482,7 @@ public class LoanAccountData {
|
||||
repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
|
||||
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
|
||||
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
|
||||
loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting);
|
||||
}
|
||||
|
||||
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,
|
||||
calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
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 Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
|
||||
final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary,
|
||||
final BigDecimal feeChargesDueAtDisbursementCharged) {
|
||||
final BigDecimal feeChargesDueAtDisbursementCharged, final Boolean syncDisbursementWithMeeting) {
|
||||
|
||||
final LoanScheduleData repaymentSchedule = null;
|
||||
final Collection<LoanTransactionData> transactions = null;
|
||||
@@ -572,7 +580,8 @@ public class LoanAccountData {
|
||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
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,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
|
||||
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,
|
||||
@@ -626,7 +636,8 @@ public class LoanAccountData {
|
||||
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||
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(
|
||||
@@ -672,7 +683,8 @@ public class LoanAccountData {
|
||||
final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
|
||||
final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions, final ChargeData chargeTemplate,
|
||||
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.accountNo = accountNo;
|
||||
this.status = status;
|
||||
@@ -714,6 +726,7 @@ public class LoanAccountData {
|
||||
this.interestChargedFromDate = interestChargedFromDate;
|
||||
this.timeline = timeline;
|
||||
this.feeChargesAtDisbursementCharged = feeChargesDueAtDisbursementCharged;
|
||||
this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
|
||||
|
||||
// totals
|
||||
this.summary = summary;
|
||||
|
||||
+36
-14
@@ -135,6 +135,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
@Column(name = "loan_status_id", nullable = false)
|
||||
private Integer loanStatus;
|
||||
|
||||
@Column(name = "sync_disbursement_with_meeting", nullable = true)
|
||||
private Boolean syncDisbursementWithMeeting;
|
||||
|
||||
// loan application states
|
||||
@Temporal(TemporalType.DATE)
|
||||
@Column(name = "submittedon_date")
|
||||
@@ -276,31 +279,34 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final Set<LoanCollateral> collateral) {
|
||||
final LoanStatus status = null;
|
||||
final Group group = null;
|
||||
final Boolean syncDisbursementWithMeeting = null;
|
||||
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,
|
||||
final LoanProduct loanProduct, final Fund fund, final Staff officer,
|
||||
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 CodeValue loanPurpose = null;
|
||||
final Set<LoanCollateral> collateral = null;
|
||||
final Client client = null;
|
||||
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,
|
||||
final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer,
|
||||
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 CodeValue loanPurpose = null;
|
||||
final Set<LoanCollateral> collateral = null;
|
||||
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||
loanRepaymentScheduleDetail, status, loanCharges, collateral);
|
||||
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting);
|
||||
}
|
||||
|
||||
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,
|
||||
final Staff loanOfficer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
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.validateRepaymentPeriodWithGraceSettings();
|
||||
@@ -348,6 +354,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.collateral = null;
|
||||
}
|
||||
this.loanOfficerHistory = null;
|
||||
|
||||
this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
|
||||
}
|
||||
|
||||
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
|
||||
// FIXME: AA - We may require separate api command to move loan from one
|
||||
// client to another
|
||||
|
||||
final String clientIdParamName = "clientId";
|
||||
final Long clientId = this.client == null ? null : this.client.getId();
|
||||
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";
|
||||
if (command.isChangeInLocalDateParameterNamed(interestChargedFromDateParamName, getInterestChargedFromDate())) {
|
||||
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,
|
||||
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();
|
||||
|
||||
@@ -1304,7 +1319,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
handleDisbursementTransaction(paymentDetail, actualDisbursementDate);
|
||||
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency);
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, firstRepaymentMeetingDate);
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
} else {
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
@@ -1331,7 +1346,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
* details/state.
|
||||
*/
|
||||
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||
final ApplicationCurrency applicationCurrency) {
|
||||
final ApplicationCurrency applicationCurrency, final LocalDate firstRepaymentMeetingDate) {
|
||||
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
||||
@@ -1341,9 +1356,11 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
final Integer loanTermFrequency = this.termFrequency;
|
||||
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,
|
||||
loanTermPeriodFrequencyType, getExpectedDisbursedOnLocalDate(), getExpectedFirstRepaymentOnDate(), getInArrearsTolerance(),
|
||||
loanTermPeriodFrequencyType, getExpectedDisbursedOnLocalDate(), firstRepaymentOnDate, getInArrearsTolerance(),
|
||||
this.loanRepaymentScheduleDetail);
|
||||
|
||||
LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges);
|
||||
@@ -2001,7 +2018,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return disbursementDate;
|
||||
}
|
||||
|
||||
private LocalDate getExpectedFirstRepaymentOnDate() {
|
||||
public LocalDate getExpectedFirstRepaymentOnDate() {
|
||||
LocalDate firstRepaymentDate = null;
|
||||
if (this.expectedFirstRepaymentOnDate != null) {
|
||||
firstRepaymentDate = new LocalDate(this.expectedFirstRepaymentOnDate);
|
||||
@@ -2299,4 +2316,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.loanSummaryWrapper = loanSummaryWrapper;
|
||||
this.transactionProcessorFactory = transactionProcessorFactory;
|
||||
}
|
||||
|
||||
public boolean isSyncDisbursementWithMeeting() {
|
||||
return this.syncDisbursementWithMeeting == null ? false : this.syncDisbursementWithMeeting;
|
||||
}
|
||||
|
||||
}
|
||||
+84
-5
@@ -16,7 +16,13 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
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.exception.LoanApplicationDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
@@ -45,18 +51,20 @@ public class LoanScheduleAssembler {
|
||||
private final LoanChargeAssembler loanChargeAssembler;
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
private final AprCalculator aprCalculator;
|
||||
private final CalendarRepository calendarRepository;
|
||||
|
||||
@Autowired
|
||||
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
|
||||
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
|
||||
final LoanScheduleGeneratorFactory loanScheduleFactory, final AprCalculator aprCalculator,
|
||||
final LoanChargeAssembler loanChargeAssembler) {
|
||||
final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanProductRepository = loanProductRepository;
|
||||
this.applicationCurrencyRepository = applicationCurrencyRepository;
|
||||
this.loanScheduleFactory = loanScheduleFactory;
|
||||
this.aprCalculator = aprCalculator;
|
||||
this.loanChargeAssembler = loanChargeAssembler;
|
||||
this.calendarRepository = calendarRepository;
|
||||
}
|
||||
|
||||
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
|
||||
@@ -104,10 +112,41 @@ public class LoanScheduleAssembler {
|
||||
// disbursement details
|
||||
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
|
||||
final Money principalMoney = Money.of(currency, principal);
|
||||
|
||||
|
||||
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
|
||||
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
||||
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
||||
@@ -125,7 +164,47 @@ public class LoanScheduleAssembler {
|
||||
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);
|
||||
return loanApplicationTerms.toLoanProductRelatedDetail();
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
|
||||
public LoanScheduleModel calculateLoanSchedule(final JsonQuery query) {
|
||||
|
||||
this.fromApiJsonDeserializer.validate(query.json());
|
||||
|
||||
|
||||
return this.loanScheduleAssembler.assembleLoanScheduleFrom(query.parsedJson());
|
||||
}
|
||||
}
|
||||
+21
-2
@@ -42,7 +42,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
|
||||
"loanTermFrequencyType", "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType",
|
||||
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment",
|
||||
"graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale",
|
||||
"dateFormat", "charges", "collateral"));
|
||||
"dateFormat", "charges", "collateral", "syncDisbursementWithMeeting"));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@@ -179,7 +179,26 @@ 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",
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
|
||||
+41
-13
@@ -47,7 +47,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
"accountNo", "externalId", "fundId", "loanOfficerId", // optional
|
||||
"loanPurposeId", "inArrearsTolerance", "charges", "collateral", // optional
|
||||
"transactionProcessingStrategyId", // settings
|
||||
"calendarId" // optional
|
||||
"calendarId", // optional
|
||||
"syncDisbursementWithMeeting"//optional
|
||||
));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
@@ -230,12 +231,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId).notNull()
|
||||
.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
|
||||
final String chargesParameterName = "charges";
|
||||
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); }
|
||||
}
|
||||
|
||||
@@ -517,12 +531,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
.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
|
||||
final String chargesParameterName = "charges";
|
||||
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) {
|
||||
final Object forceError = null;
|
||||
baseDataValidator.reset().anyOfNotNull(forceError);
|
||||
@@ -639,4 +666,5 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"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.PlatformApiDataValidationException;
|
||||
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.stereotype.Component;
|
||||
|
||||
@@ -66,6 +70,23 @@ public final class LoanEventApiJsonValidator {
|
||||
|
||||
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) {
|
||||
|
||||
@@ -121,6 +142,22 @@ public final class LoanEventApiJsonValidator {
|
||||
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) {
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
|
||||
|
||||
+16
-69
@@ -12,7 +12,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
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.CalendarRepository;
|
||||
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.AccountNumberGeneratorFactory;
|
||||
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.LoanStatus;
|
||||
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.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
@@ -156,16 +152,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
|
||||
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);
|
||||
|
||||
this.loanRepository.save(newLoanApplication);
|
||||
@@ -184,12 +170,19 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
}
|
||||
|
||||
// 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(),
|
||||
CalendarEntityType.LOANS.getValue());
|
||||
this.calendarInstanceRepository.save(calendarInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(newLoanApplication.getId()) //
|
||||
@@ -209,14 +202,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
|
||||
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);
|
||||
|
||||
if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(
|
||||
@@ -309,6 +294,13 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
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,
|
||||
CalendarEntityType.LOANS.getValue());
|
||||
if (calendar != null) {
|
||||
@@ -534,49 +526,4 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
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 transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element);
|
||||
final Long loanPurposeId = fromApiJsonHelper.extractLongNamed("loanPurposeId", element);
|
||||
final Boolean syncDisbursementWithMeeting = fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
|
||||
|
||||
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
|
||||
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
|
||||
@@ -154,7 +155,7 @@ public class LoanAssembler {
|
||||
if (group == null) { throw new GroupNotFoundException(groupId); }
|
||||
|
||||
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
|
||||
loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges);
|
||||
loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting);
|
||||
}
|
||||
|
||||
if (client != null && group != null) {
|
||||
@@ -162,7 +163,7 @@ public class LoanAssembler {
|
||||
if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); }
|
||||
|
||||
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);
|
||||
|
||||
+6
-2
@@ -442,7 +442,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
+ " la.fee_charges_overdue_derived as feeChargesOverdue,"
|
||||
+ " la.penalty_charges_overdue_derived as penaltyChargesOverdue,"
|
||||
+ " 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" //
|
||||
+ " join m_product_loan lp on lp.id = l.product_id" //
|
||||
+ " 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 interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate");
|
||||
|
||||
final Boolean syncDisbursementWithMeeting = rs.getBoolean("syncDisbursementWithMeeting");
|
||||
|
||||
final BigDecimal feeChargesDueAtDisbursementCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
|
||||
"feeChargesDueAtDisbursementCharged");
|
||||
LoanSummaryData loanSummary = null;
|
||||
@@ -652,7 +655,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId,
|
||||
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
|
||||
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged);
|
||||
graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged,
|
||||
syncDisbursementWithMeeting);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+60
-41
@@ -8,7 +8,6 @@ package org.mifosplatform.portfolio.loanaccount.service;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
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.CalendarInstance;
|
||||
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.charge.domain.Charge;
|
||||
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.LoanTransaction;
|
||||
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.LoanOfficerAssignmentException;
|
||||
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.serialization.LoanEventApiJsonValidator;
|
||||
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.note.domain.Note;
|
||||
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.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Service
|
||||
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
||||
@@ -139,12 +136,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.loanEventApiJsonValidator.validateDisbursement(command.json());
|
||||
|
||||
final Loan loan = retrieveLoanBy(loanId);
|
||||
|
||||
// validate actual disbursement date against meeting date
|
||||
Collection<CalendarInstance> calendarInstances = this.calendarInstanceRepository.findByEntityIdAndEntityTypeId(loan.getId(),
|
||||
// validate actual disbursement date against meeting date
|
||||
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(),
|
||||
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 ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
|
||||
@@ -155,8 +155,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
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,
|
||||
existingReversedTransactionIds, changes, paymentDetail);
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentMeetingDate);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.loanRepository.save(loan);
|
||||
@@ -181,36 +185,39 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.build();
|
||||
}
|
||||
|
||||
private void validateDisbursementDate(final LocalDate disbursementDate, final Collection<CalendarInstance> calendarInstances) {
|
||||
if (!CollectionUtils.isEmpty(calendarInstances)) {
|
||||
Calendar calendar = null;
|
||||
for (CalendarInstance calendarInstance : calendarInstances) {
|
||||
calendar = calendarInstance.getCalendar();
|
||||
break;
|
||||
}
|
||||
if (calendar != null && disbursementDate != null) {
|
||||
|
||||
// Actual disbursement date cannot be before Meeting start
|
||||
// date
|
||||
if (disbursementDate.isBefore(calendar.getStartDateLocalDate())) {
|
||||
final String errorMessage = "Actual disbursement date '" + disbursementDate.toString()
|
||||
+ "' cannot be before meeting start date '" + calendar.getStartDate().toString() + "' ";
|
||||
throw new LoanApplicationDateException("actual.disbursement.date.cannot.be.before.meeting.start.date", errorMessage,
|
||||
disbursementDate.toString(), calendar.getStartDate());
|
||||
}
|
||||
|
||||
// Disbursement date should fall on a meeting date
|
||||
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), disbursementDate)) {
|
||||
final String errorMessage = "Expected disbursement date '" + disbursementDate.toString()
|
||||
+ "' does not fall on a meeting date.";
|
||||
throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage, disbursementDate.toString(),
|
||||
calendar.getTitle());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
private LocalDate getFirstRepaymentMeetingDate(
|
||||
final LocalDate actualDisbursementDate, final Loan loan,
|
||||
final CalendarInstance calendarInstance) {
|
||||
final Calendar calendar = (calendarInstance == null) ? null
|
||||
: calendarInstance.getCalendar();
|
||||
LocalDate firstRepaymentMeetingDate = null;
|
||||
if (calendar != null) {// sync repayments
|
||||
|
||||
// TODO: AA - user provided first repayment date takes precedence over recalculated meeting date
|
||||
if (loan.getExpectedFirstRepaymentOnDate() == 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 LoanProductRelatedDetail repaymentScheduleDetails = loan
|
||||
.repaymentScheduleDetail();
|
||||
if (repaymentScheduleDetails != null) {// Not expecting to be
|
||||
// null
|
||||
final Integer repayEvery = repaymentScheduleDetails
|
||||
.getRepayEvery();
|
||||
final String frequency = CalendarHelper
|
||||
.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
|
||||
.getRepaymentPeriodFrequencyType());
|
||||
firstRepaymentMeetingDate = CalendarHelper
|
||||
.getFirstRepaymentMeetingDate(calendar,
|
||||
actualDisbursementDate, repayEvery,
|
||||
frequency);
|
||||
}
|
||||
}
|
||||
}
|
||||
return firstRepaymentMeetingDate;
|
||||
}
|
||||
|
||||
/****
|
||||
* 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);
|
||||
|
||||
// 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,
|
||||
existingReversedTransactionIds, changes, paymentDetail);
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentOnDate);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.loanRepository.save(loan);
|
||||
@@ -327,7 +338,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private CommandProcessingResultBuilder saveLoanRepayment(final Long loanId, final PaymentDetail paymentDetail,
|
||||
final BigDecimal transactionAmount, final LocalDate transactionDate, final String noteText) {
|
||||
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> existingReversedTransactionIds = new ArrayList<Long>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user