Merge pull request #436 from ashok-conflux/MIFOSX-76
MIFOSX-76: Apply holidays to loan repayment schedule
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
INSERT INTO `c_configuration` (`name`) VALUES ('reschedule-repayments-on-holidays');
|
||||
+1
@@ -10,4 +10,5 @@ public interface ConfigurationDomainService {
|
||||
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
||||
boolean isAmazonS3Enabled();
|
||||
boolean isRescheduleFutureRepaymentsEnabled();
|
||||
boolean isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
}
|
||||
+12
@@ -52,5 +52,17 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(rescheduleRepaymentsConfigurationProperty); }
|
||||
return property.isEnabled();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService#isHolidaysEnabled()
|
||||
*/
|
||||
@Override
|
||||
public boolean isRescheduleRepaymentsOnHolidaysEnabled() {
|
||||
final String holidaysConfigurationProperty = "reschedule-repayments-on-holidays";
|
||||
final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByName(holidaysConfigurationProperty);
|
||||
if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(holidaysConfigurationProperty); }
|
||||
return property.isEnabled();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+9
@@ -5,7 +5,10 @@
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.core.service;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
@@ -50,4 +53,10 @@ public class DateUtils {
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatToSqlDate(final Date date){
|
||||
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||
final String formattedSqlDate = df.format(date);
|
||||
return formattedSqlDate;
|
||||
}
|
||||
}
|
||||
+32
-5
@@ -34,12 +34,10 @@ public class Holiday extends AbstractPersistable<Long> {
|
||||
@Column(name = "name", unique = true, nullable = false, length = 100)
|
||||
private String name;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Column(name = "from_date", nullable = false)
|
||||
@Temporal(TemporalType.DATE)
|
||||
private Date fromDate;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Column(name = "to_date", nullable = false)
|
||||
@Temporal(TemporalType.DATE)
|
||||
private Date toDate;
|
||||
@@ -55,7 +53,6 @@ public class Holiday extends AbstractPersistable<Long> {
|
||||
@Column(name = "description", length = 100)
|
||||
private String description;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
@JoinTable(name = "m_holiday_office", joinColumns = @JoinColumn(name = "holiday_id"), inverseJoinColumns = @JoinColumn(name = "office_id"))
|
||||
private Set<Office> offices;
|
||||
@@ -99,12 +96,42 @@ public class Holiday extends AbstractPersistable<Long> {
|
||||
this.offices = offices;
|
||||
}
|
||||
}
|
||||
|
||||
protected Holiday() {}
|
||||
|
||||
public Date getRepaymentsRescheduledTo() {
|
||||
return this.repaymentsRescheduledTo;
|
||||
public LocalDate getRepaymentsRescheduledToLocalDate() {
|
||||
LocalDate repaymentsRescheduledTo = null;
|
||||
if (this.repaymentsRescheduledTo != null) {
|
||||
repaymentsRescheduledTo = new LocalDate(this.repaymentsRescheduledTo);
|
||||
}
|
||||
return repaymentsRescheduledTo;
|
||||
}
|
||||
|
||||
public boolean isProcessed() {
|
||||
return this.processed;
|
||||
}
|
||||
|
||||
public Set<Office> getOffices() {
|
||||
return this.offices;
|
||||
}
|
||||
|
||||
public LocalDate getFromDateLocalDate() {
|
||||
LocalDate fromDate = null;
|
||||
if (this.fromDate != null) {
|
||||
fromDate = new LocalDate(this.fromDate);
|
||||
}
|
||||
return fromDate;
|
||||
}
|
||||
|
||||
public LocalDate getToDateLocalDate() {
|
||||
LocalDate toDate = null;
|
||||
if (this.toDate != null) {
|
||||
toDate = new LocalDate(this.toDate);
|
||||
}
|
||||
return toDate;
|
||||
}
|
||||
|
||||
public void processed(){
|
||||
this.processed = true;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -5,11 +5,20 @@
|
||||
*/
|
||||
package org.mifosplatform.organisation.holiday.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
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 HolidayRepository extends JpaRepository<Holiday, Long>,
|
||||
JpaSpecificationExecutor<Holiday> {
|
||||
// no added behaviour
|
||||
@Query("select holiday from Holiday holiday, IN(holiday.offices) office where holiday.fromDate >= :fromDate and office.id = :officeId")
|
||||
List<Holiday> findByOfficeIdAndGreaterThanDate(@Param("officeId") Long officeId, @Param("fromDate") Date fromDate);
|
||||
|
||||
@Query("from Holiday holiday where holiday.processed = false")
|
||||
List<Holiday> findUnprocessed();
|
||||
}
|
||||
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package org.mifosplatform.organisation.holiday.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
|
||||
public class HolidayUtil {
|
||||
|
||||
public static LocalDate getRepaymentRescheduleDateToIfHoliday(final LocalDate repaymentDate, final List<Holiday> holidays) {
|
||||
for (final Holiday holiday : holidays) {
|
||||
if (repaymentDate.equals(holiday.getFromDateLocalDate()) || repaymentDate.equals(holiday.getToDateLocalDate())
|
||||
|| (repaymentDate.isAfter(holiday.getFromDateLocalDate()) && repaymentDate.isBefore(holiday.getToDateLocalDate()))) {
|
||||
// should be take from holiday
|
||||
return holiday.getRepaymentsRescheduledToLocalDate();
|
||||
}
|
||||
}
|
||||
return repaymentDate;
|
||||
}
|
||||
}
|
||||
+39
-8
@@ -46,6 +46,8 @@ import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.service.DateUtils;
|
||||
import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.holiday.service.HolidayUtil;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
@@ -1312,8 +1314,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 LocalDate firstRepaymentMeetingDate) {
|
||||
final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges, final PaymentDetail paymentDetail,
|
||||
final LocalDate firstRepaymentMeetingDate, final boolean isHolidayEnabled, final List<Holiday> holidays) {
|
||||
|
||||
updateLoanToPreDisbursalState();
|
||||
|
||||
@@ -1339,7 +1341,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
handleDisbursementTransaction(paymentDetail, actualDisbursementDate);
|
||||
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, firstRepaymentMeetingDate);
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, firstRepaymentMeetingDate, isHolidayEnabled, holidays);
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
} else {
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
@@ -1366,7 +1368,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
* details/state.
|
||||
*/
|
||||
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||
final ApplicationCurrency applicationCurrency, final LocalDate firstRepaymentMeetingDate) {
|
||||
final ApplicationCurrency applicationCurrency, final LocalDate firstRepaymentMeetingDate, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays) {
|
||||
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
||||
@@ -1380,10 +1383,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
//to generate repayment schedule dates
|
||||
final LocalDate firstRepaymentOnDate = (getExpectedFirstRepaymentOnDate() == null) ? firstRepaymentMeetingDate : getExpectedFirstRepaymentOnDate();
|
||||
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
|
||||
loanTermPeriodFrequencyType, getExpectedDisbursedOnLocalDate(), firstRepaymentOnDate, getInArrearsTolerance(),
|
||||
loanTermPeriodFrequencyType, getDisbursementDate(), firstRepaymentOnDate, getInArrearsTolerance(),
|
||||
this.loanRepaymentScheduleDetail);
|
||||
|
||||
LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges);
|
||||
LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges, isHolidayEnabled, holidays);
|
||||
|
||||
updateLoanSchedule(loanSchedule);
|
||||
}
|
||||
@@ -2030,7 +2033,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return expectedDisbursementDate;
|
||||
}
|
||||
|
||||
private LocalDate getDisbursementDate() {
|
||||
public LocalDate getDisbursementDate() {
|
||||
LocalDate disbursementDate = getExpectedDisbursedOnLocalDate();
|
||||
if (this.actualDisbursementDate != null) {
|
||||
disbursementDate = new LocalDate(this.actualDisbursementDate);
|
||||
@@ -2345,7 +2348,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return this.closedOnDate;
|
||||
}
|
||||
|
||||
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule) {
|
||||
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, final boolean isHolidayEnabled, final List<Holiday> holidays) {
|
||||
//update repayment dates of repayment schedule installaments
|
||||
LocalDate tmpFromDate = this.getDisbursementDate();//first repayment's from date is same as disbursement date.
|
||||
LocalDate newRepaymentDate = null;
|
||||
@@ -2358,6 +2361,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery();
|
||||
// reset repayment date with new meeting date
|
||||
newRepaymentDate = CalendarHelper.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate, loanRepaymentInterval, frequency);
|
||||
if(isHolidayEnabled){
|
||||
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
|
||||
}
|
||||
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
|
||||
//reset from date to get actual daysInPeriod
|
||||
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
|
||||
@@ -2368,6 +2374,31 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void applyHolidayToRepaymentScheduleDates(final Holiday holiday) {
|
||||
// first repayment's from date is same as disbursement date.
|
||||
LocalDate tmpFromDate = this.getDisbursementDate();
|
||||
// Loop through all loanRepayments
|
||||
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
|
||||
final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
|
||||
|
||||
//update from date if it's not same as previous installament's due date.
|
||||
if(!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)){
|
||||
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
|
||||
}
|
||||
if (oldDueDate.isAfter(holiday.getToDateLocalDate())) break;
|
||||
|
||||
if (oldDueDate.equals(holiday.getFromDateLocalDate()) || oldDueDate.equals(holiday.getToDateLocalDate())
|
||||
|| (oldDueDate.isAfter(holiday.getFromDateLocalDate()) && oldDueDate.isBefore(holiday.getToDateLocalDate()))) {
|
||||
// should be take from holiday
|
||||
final LocalDate newRepaymentDate = holiday.getRepaymentsRescheduledToLocalDate();
|
||||
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
|
||||
}
|
||||
tmpFromDate = loanRepaymentScheduleInstallment.getDueDate();
|
||||
}
|
||||
}
|
||||
|
||||
public Group group() {
|
||||
return this.group;
|
||||
|
||||
+30
-19
@@ -33,29 +33,19 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
||||
|
||||
public static final String FIND_MAX_CLIENT_OR_JLG_LOAN_PRODUCT_COUNTER_QUERY = "Select MAX(l.loanProductCounter) from Loan l where "
|
||||
+ "l.client.id = :clientId and l.loanProduct.id = :productId";
|
||||
|
||||
|
||||
public static final String FIND_GROUP_LOANS_TO_UPDATE = "from Loan l where l.loanCounter > :loanCounter and "
|
||||
+ "l.group.id = :groupId and l.loanType = :groupLoanType order by l.loanCounter";
|
||||
|
||||
|
||||
public static final String FIND_CLIENT_OR_JLG_LOANS_TO_UPDATE = "from Loan l where l.loanCounter > :loanCounter and "
|
||||
+ "l.client.id = :clientId order by l.loanCounter";
|
||||
|
||||
|
||||
public static final String FIND_GROUP_LOANS_TO_UPDATE_LOANPRODUCT_COUNTER = "from Loan l where l.loanProductCounter > :loanProductCounter"
|
||||
+ " and l.group.id = :groupId and l.loanType = :groupLoanType and l.loanCounter is NULL order by l.loanProductCounter";
|
||||
|
||||
|
||||
public static final String FIND_CLIENT_LOANS_TO_UPDATE_LOANPRODUCT_COUNTER = "from Loan l where l.loanProductCounter > :loanProductCounter"
|
||||
+ " and l.client.id = :clientId and l.loanCounter is NULL order by l.loanProductCounter";
|
||||
|
||||
@Query("from Loan loan where loan.client.id = :clientId and loan.group.id = :groupId")
|
||||
List<Loan> findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId);
|
||||
|
||||
@Query("from Loan loan where loan.client.id = :clientId")
|
||||
List<Loan> findLoanByClientId(@Param("clientId") Long clientId);
|
||||
|
||||
@Query("from Loan loan where loan.id IN :ids and loan.loanStatus IN :loanStatuses and loan.loanType IN :loanTypes")
|
||||
List<Loan> findByIdsAndLoanStatusAndLoanType(@Param("ids") Collection<Long> ids,
|
||||
@Param("loanStatuses") Collection<Integer> loanStatuses, @Param("loanTypes") Collection<Integer> loanTypes);
|
||||
|
||||
@Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
|
||||
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
|
||||
@Param("loanType") Integer loanType);
|
||||
@@ -82,11 +72,32 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
||||
|
||||
@Query(FIND_CLIENT_OR_JLG_LOANS_TO_UPDATE)
|
||||
List<Loan> getClientOrJLGLoansToUpdateLoanCounter(@Param("loanCounter") Integer loanCounter, @Param("clientId") Long clientId);
|
||||
|
||||
|
||||
@Query(FIND_GROUP_LOANS_TO_UPDATE_LOANPRODUCT_COUNTER)
|
||||
List<Loan> getGroupLoansToUpdateLoanProductCounter(@Param("loanProductCounter") Integer loanProductCounter, @Param("groupId") Long groupId,
|
||||
@Param("groupLoanType") Integer groupLoanType);
|
||||
|
||||
List<Loan> getGroupLoansToUpdateLoanProductCounter(@Param("loanProductCounter") Integer loanProductCounter,
|
||||
@Param("groupId") Long groupId, @Param("groupLoanType") Integer groupLoanType);
|
||||
|
||||
@Query(FIND_CLIENT_LOANS_TO_UPDATE_LOANPRODUCT_COUNTER)
|
||||
List<Loan> getClientLoansToUpdateLoanProductCounter(@Param("loanProductCounter") Integer loanProductCounter, @Param("clientId") Long clientId);
|
||||
List<Loan> getClientLoansToUpdateLoanProductCounter(@Param("loanProductCounter") Integer loanProductCounter,
|
||||
@Param("clientId") Long clientId);
|
||||
|
||||
@Query("from Loan loan where loan.client.id = :clientId and loan.group.id = :groupId")
|
||||
List<Loan> findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId);
|
||||
|
||||
@Query("from Loan loan where loan.client.id = :clientId")
|
||||
List<Loan> findLoanByClientId(@Param("clientId") Long clientId);
|
||||
|
||||
@Query("from Loan loan where loan.id IN :ids and loan.loanStatus IN :loanStatuses and loan.loanType IN :loanTypes")
|
||||
List<Loan> findByIdsAndLoanStatusAndLoanType(@Param("ids") Collection<Long> ids,
|
||||
@Param("loanStatuses") Collection<Integer> loanStatuses, @Param("loanTypes") Collection<Integer> loanTypes);
|
||||
|
||||
@Query("select loan.id from Loan loan where loan.actualDisbursementDate > :disbursalDate order by loan.actualDisbursementDate")
|
||||
List<Long> getLoansDisbursedAfter(@Param("disbursalDate") Date disbursalDate);
|
||||
|
||||
@Query("from Loan loan where loan.client.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
|
||||
List<Loan> findByClientOfficeIdsAndLoanStatus(@Param("officeIds") Collection<Long> officeIds, @Param("loanStatuses") Collection<Integer> loanStatuses);
|
||||
|
||||
@Query("from Loan loan where loan.group.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
|
||||
List<Loan> findByGroupOfficeIdsAndLoanStatus(@Param("officeIds") Collection<Long> officeIds, @Param("loanStatuses") Collection<Integer> loanStatuses);
|
||||
|
||||
}
|
||||
+3
-2
@@ -14,6 +14,7 @@ import java.util.Set;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
@@ -29,10 +30,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
@Override
|
||||
public LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency,
|
||||
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges) {
|
||||
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final List<Holiday> holidays) {
|
||||
|
||||
// 1. generate list of proposed schedule due dates
|
||||
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms);
|
||||
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms, isHolidayEnabled, holidays);
|
||||
|
||||
// 2. determine the total charges due at time of disbursement
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||
|
||||
+13
-4
@@ -9,12 +9,15 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.holiday.service.HolidayUtil;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
|
||||
@Override
|
||||
public List<LocalDate> generate(final LoanApplicationTerms loanApplicationTerms) {
|
||||
public List<LocalDate> generate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays) {
|
||||
|
||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||
|
||||
@@ -50,11 +53,17 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dueRepaymentPeriodDates.add(dueRepaymentPeriodDate);
|
||||
|
||||
//start date should be assigned before applying holidays to avoid the reset of next repayment dates.
|
||||
startDate = dueRepaymentPeriodDate;
|
||||
//Apply holidays
|
||||
if(isHolidayEnabled){
|
||||
dueRepaymentPeriodDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(dueRepaymentPeriodDate, holidays);
|
||||
}
|
||||
|
||||
dueRepaymentPeriodDates.add(dueRepaymentPeriodDate);
|
||||
}
|
||||
|
||||
|
||||
return dueRepaymentPeriodDates;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -6,13 +6,15 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
|
||||
public interface LoanScheduleGenerator {
|
||||
|
||||
LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms,
|
||||
Set<LoanCharge> loanCharges);
|
||||
Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays);
|
||||
}
|
||||
+2
-1
@@ -8,11 +8,12 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public interface ScheduledDateGenerator {
|
||||
|
||||
List<LocalDate> generate(LoanApplicationTerms loanApplicationTerms);
|
||||
List<LocalDate> generate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays);
|
||||
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
|
||||
List<LocalDate> scheduledDates);
|
||||
|
||||
+102
-65
@@ -8,10 +8,14 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
@@ -21,6 +25,10 @@ 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.client.domain.Client;
|
||||
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.group.domain.Group;
|
||||
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
@@ -52,12 +60,18 @@ public class LoanScheduleAssembler {
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
private final AprCalculator aprCalculator;
|
||||
private final CalendarRepository calendarRepository;
|
||||
private final HolidayRepository holidayRepository;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final ClientRepositoryWrapper clientRepository;
|
||||
private final GroupRepositoryWrapper groupRepository;
|
||||
|
||||
@Autowired
|
||||
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
|
||||
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
|
||||
final LoanScheduleGeneratorFactory loanScheduleFactory, final AprCalculator aprCalculator,
|
||||
final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository) {
|
||||
final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository,
|
||||
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
|
||||
final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanProductRepository = loanProductRepository;
|
||||
this.applicationCurrencyRepository = applicationCurrencyRepository;
|
||||
@@ -65,6 +79,10 @@ public class LoanScheduleAssembler {
|
||||
this.aprCalculator = aprCalculator;
|
||||
this.loanChargeAssembler = loanChargeAssembler;
|
||||
this.calendarRepository = calendarRepository;
|
||||
this.holidayRepository = holidayRepository;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.clientRepository = clientRepository;
|
||||
this.groupRepository = groupRepository;
|
||||
}
|
||||
|
||||
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
|
||||
@@ -119,32 +137,33 @@ public class LoanScheduleAssembler {
|
||||
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 ((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);
|
||||
|
||||
// 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 (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);
|
||||
// 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
|
||||
@@ -164,59 +183,77 @@ public class LoanScheduleAssembler {
|
||||
graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney);
|
||||
}
|
||||
|
||||
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 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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) {
|
||||
LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
|
||||
return loanApplicationTerms.toLoanProductRelatedDetail();
|
||||
}
|
||||
|
||||
public LoanScheduleModel assembleLoanScheduleFrom(final JsonElement element) {
|
||||
|
||||
//This method is getting called from calculate loan schedule.
|
||||
final LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
|
||||
|
||||
return assembleLoanScheduleFrom(loanApplicationTerms, element);
|
||||
//Get holiday details
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
|
||||
final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element);
|
||||
final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element);
|
||||
|
||||
Client client = null;
|
||||
Group group = null;
|
||||
Long officeId = null;
|
||||
if(clientId != null){
|
||||
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
officeId = client.getOffice().getId();
|
||||
}else if (groupId != null){
|
||||
group = this.groupRepository.findOneWithNotFoundDetection(groupId);
|
||||
officeId = group.getOffice().getId();
|
||||
}
|
||||
|
||||
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, expectedDisbursementDate.toDate());
|
||||
return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, element);
|
||||
}
|
||||
|
||||
public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final JsonElement element) {
|
||||
public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final JsonElement element) {
|
||||
|
||||
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element);
|
||||
|
||||
@@ -228,6 +265,6 @@ public class LoanScheduleAssembler {
|
||||
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
|
||||
return loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, loanCharges);
|
||||
return loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -159,7 +159,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(newLoanApplication.getTermFrequency(), newLoanApplication.getTermPeriodFrequencyType(),
|
||||
productRelatedDetail.getNumberOfRepayments(), productRelatedDetail.getRepayEvery(),
|
||||
productRelatedDetail.getRepaymentPeriodFrequencyType().getValue());
|
||||
|
||||
|
||||
this.loanRepository.save(newLoanApplication);
|
||||
|
||||
if (newLoanApplication.isAccountNumberRequiresAutoGeneration()) {
|
||||
|
||||
+12
-2
@@ -12,9 +12,12 @@ import java.util.Set;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
|
||||
import org.mifosplatform.organisation.staff.domain.Staff;
|
||||
import org.mifosplatform.organisation.staff.domain.StaffRepository;
|
||||
import org.mifosplatform.organisation.staff.exception.StaffNotFoundException;
|
||||
@@ -71,6 +74,8 @@ public class LoanAssembler {
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanSummaryWrapper loanSummaryWrapper;
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
|
||||
private final HolidayRepository holidayRepository;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
|
||||
@Autowired
|
||||
public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
|
||||
@@ -79,7 +84,8 @@ public class LoanAssembler {
|
||||
final StaffRepository staffRepository, final CodeValueRepositoryWrapper codeValueRepository,
|
||||
final LoanScheduleAssembler loanScheduleAssembler, final LoanChargeAssembler loanChargeAssembler,
|
||||
final CollateralAssembler loanCollateralAssembler, final LoanSummaryWrapper loanSummaryWrapper,
|
||||
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) {
|
||||
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
|
||||
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanProductRepository = loanProductRepository;
|
||||
this.clientRepository = clientRepository;
|
||||
@@ -93,6 +99,8 @@ public class LoanAssembler {
|
||||
this.loanCollateralAssembler = loanCollateralAssembler;
|
||||
this.loanSummaryWrapper = loanSummaryWrapper;
|
||||
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
|
||||
this.holidayRepository = holidayRepository;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
}
|
||||
|
||||
public Loan assembleFrom(final JsonCommand command, final AppUser currentUser) {
|
||||
@@ -174,7 +182,9 @@ public class LoanAssembler {
|
||||
this.loanRepaymentScheduleTransactionProcessorFactory);
|
||||
|
||||
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
|
||||
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, element);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(), loanApplicationTerms.getExpectedDisbursementDate().toDate());
|
||||
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, element);
|
||||
|
||||
loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(),
|
||||
submittedOnDate, externalId);
|
||||
|
||||
+2
@@ -52,4 +52,6 @@ public interface LoanWritePlatformService {
|
||||
CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command);
|
||||
|
||||
void updateRepaymentsSchedule(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
|
||||
|
||||
void applyHolidaysToLoans();
|
||||
}
|
||||
+58
-4
@@ -12,18 +12,23 @@ import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
|
||||
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.organisation.office.domain.Office;
|
||||
import org.mifosplatform.organisation.staff.domain.Staff;
|
||||
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
|
||||
@@ -94,6 +99,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
|
||||
private final HolidayRepository holidayRepository;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
|
||||
@Autowired
|
||||
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
|
||||
@@ -105,7 +112,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanSummaryWrapper loanSummaryWrapper,
|
||||
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
|
||||
final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository,
|
||||
final PaymentDetailWritePlatformService paymentDetailWritePlatformService) {
|
||||
final PaymentDetailWritePlatformService paymentDetailWritePlatformService,
|
||||
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService) {
|
||||
this.context = context;
|
||||
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
|
||||
this.loanAssembler = loanAssembler;
|
||||
@@ -122,6 +130,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.loanScheduleFactory = loanScheduleFactory;
|
||||
this.calendarInstanceRepository = calendarInstanceRepository;
|
||||
this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
|
||||
this.holidayRepository = holidayRepository;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
}
|
||||
|
||||
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
|
||||
@@ -160,11 +170,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
//Recalculate first repayment date based in actual disbursement date.
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
final LocalDate firstRepaymentMeetingDate = getFirstRepaymentMeetingDate(actualDisbursementDate, loan, calendarInstance);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), actualDisbursementDate.toDate());
|
||||
|
||||
updateLoanCounters(loan, actualDisbursementDate);
|
||||
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentMeetingDate);
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentMeetingDate, isHolidayEnabled, holidays);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.loanRepository.save(loan);
|
||||
@@ -250,8 +262,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
//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;
|
||||
final boolean isHolidayEnabled = false;
|
||||
final List<Holiday> holidays = null;
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentOnDate);
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentOnDate, isHolidayEnabled, holidays);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.loanRepository.save(loan);
|
||||
@@ -990,9 +1004,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void updateRepaymentsSchedule(final Calendar calendar, Collection<CalendarInstance> loanCalendarInstances) {
|
||||
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final Collection<Integer> loanStatuses = new ArrayList<Integer>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
|
||||
LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
|
||||
final Collection<Integer> loanTypes = new ArrayList<Integer>(Arrays.asList(LoanType.GROUP.getValue(), LoanType.JLG.getValue()));
|
||||
@@ -1003,10 +1019,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
final List<Loan> loans = this.loanRepository.findByIdsAndLoanStatusAndLoanType(loanIds, loanStatuses, loanTypes);
|
||||
List<Holiday> holidays = null;
|
||||
//loop through each loan to reschedule the repayment dates
|
||||
for (Loan loan : loans) {
|
||||
if(loan != null){
|
||||
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence());
|
||||
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
|
||||
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, holidays);
|
||||
this.loanRepository.save(loan);
|
||||
}
|
||||
}
|
||||
@@ -1157,4 +1175,40 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
this.loanRepository.save(loansToUpdate);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void applyHolidaysToLoans() {
|
||||
|
||||
final Collection<Integer> loanStatuses = new ArrayList<Integer>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
|
||||
LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
|
||||
// Get all Holidays which are yet to applied to loans
|
||||
List<Holiday> holidays = this.holidayRepository.findUnprocessed();
|
||||
|
||||
// Loop through all holidays
|
||||
for (Holiday holiday : holidays) {
|
||||
// All offices to which holiday is applied
|
||||
final Set<Office> offices = holiday.getOffices();
|
||||
Collection<Long> officeIds = new ArrayList<Long>(offices.size());
|
||||
for (Office office : offices) {
|
||||
officeIds.add(office.getId());
|
||||
}
|
||||
|
||||
// get all loans
|
||||
List<Loan> loans = new ArrayList<Loan>();
|
||||
//get all individual and jlg loans
|
||||
loans.addAll(this.loanRepository.findByClientOfficeIdsAndLoanStatus(officeIds, loanStatuses));
|
||||
//FIXME: optimize to get all client and group loans belongs to a office id
|
||||
//get all group loans
|
||||
loans.addAll(this.loanRepository.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses));
|
||||
|
||||
for (Loan loan : loans) {
|
||||
// apply holiday
|
||||
loan.applyHolidayToRepaymentScheduleDates(holiday);
|
||||
}
|
||||
this.loanRepository.save(loans);
|
||||
holiday.processed();
|
||||
}
|
||||
this.holidayRepository.save(holidays);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.scheduledjobs;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.mifosplatform.scheduledjobs.service.ScheduledJobRunnerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ApplyHolidaysToLoansScheduledJob {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ApplyHolidaysToLoansScheduledJob.class);
|
||||
|
||||
private final ScheduledJobRunnerService scheduledJobRunnerService;
|
||||
|
||||
public ApplyHolidaysToLoansScheduledJob(final ScheduledJobRunnerService scheduledJobRunnerService) {
|
||||
this.scheduledJobRunnerService = scheduledJobRunnerService;
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
logger.info("Running Job ApplyHolidaysToLoansScheduledJob " + new Date());
|
||||
|
||||
this.scheduledJobRunnerService.applyHolidaysToLoans();
|
||||
|
||||
logger.info("Finishing Job ApplyHolidaysToLoansScheduledJob " + new Date());
|
||||
}
|
||||
}
|
||||
+2
@@ -14,4 +14,6 @@ public interface ScheduledJobRunnerService {
|
||||
void updateLoanPaidInAdvance();
|
||||
|
||||
void applyAnnualFeeForSavings();
|
||||
|
||||
void applyHolidaysToLoans();
|
||||
}
|
||||
|
||||
+15
-1
@@ -11,6 +11,7 @@ import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
|
||||
import org.mifosplatform.infrastructure.core.service.DataSourcePerTenantService;
|
||||
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
|
||||
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -27,13 +28,16 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
|
||||
private final TenantDetailsService tenantDetailsService;
|
||||
private final DataSourcePerTenantService dataSourcePerTenantService;
|
||||
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
|
||||
private final LoanWritePlatformService loanWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public ScheduledJobRunnerServiceImpl(final DataSourcePerTenantService dataSourcePerTenantService,
|
||||
final TenantDetailsService tenantDetailsService, final SavingsAccountWritePlatformService savingsAccountWritePlatformService) {
|
||||
final TenantDetailsService tenantDetailsService, final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
final LoanWritePlatformService loanWritePlatformService) {
|
||||
this.dataSourcePerTenantService = dataSourcePerTenantService;
|
||||
this.tenantDetailsService = tenantDetailsService;
|
||||
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
|
||||
this.loanWritePlatformService = loanWritePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -220,4 +224,14 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
|
||||
logger.info(tenant.getName() + ": Results affected by update: " + savingsIds.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void applyHolidaysToLoans() {
|
||||
List<MifosPlatformTenant> allTenants = this.tenantDetailsService.findAllTenants();
|
||||
for (MifosPlatformTenant tenant : allTenants) {
|
||||
ThreadLocalContextUtil.setTenant(tenant);
|
||||
this.loanWritePlatformService.applyHolidaysToLoans();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,11 @@
|
||||
class="org.mifosplatform.scheduledjobs.ApplyAnnualFeeForSavingsScheduledJob">
|
||||
<constructor-arg ref="scheduledJobRunnerService" />
|
||||
</bean>
|
||||
|
||||
<bean id="applyHolidaysToLoansScheduledJob"
|
||||
class="org.mifosplatform.scheduledjobs.ApplyHolidaysToLoansScheduledJob">
|
||||
<constructor-arg ref="scheduledJobRunnerService" />
|
||||
</bean>
|
||||
|
||||
<bean id="updateLoanSummariesJobDetail"
|
||||
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="group" value="mifosJobs" />
|
||||
@@ -58,6 +62,14 @@
|
||||
<property name="targetMethod" value="execute" />
|
||||
<property name="concurrent" value="false" />
|
||||
</bean>
|
||||
|
||||
<bean id="applyHolidaysToLoansJobDetail"
|
||||
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="group" value="mifosJobs" />
|
||||
<property name="targetObject" ref="applyHolidaysToLoansScheduledJob" />
|
||||
<property name="targetMethod" value="execute" />
|
||||
<property name="concurrent" value="false" />
|
||||
</bean>
|
||||
|
||||
<bean id="updateLoanSummariesJobCronTrigger"
|
||||
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
|
||||
@@ -83,6 +95,12 @@
|
||||
<property name="cronExpression" value="0 20 22 1/1 * ? *" />
|
||||
</bean>
|
||||
|
||||
<bean id="applyHolidaysToJobDetailCronTrigger"
|
||||
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
|
||||
<property name="jobDetail" ref="applyHolidaysToLoansJobDetail" />
|
||||
<property name="cronExpression" value="0 0 12 * * ?" />
|
||||
</bean>
|
||||
|
||||
<bean name="schedulerFactory"
|
||||
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
||||
<property name="triggers">
|
||||
@@ -91,6 +109,7 @@
|
||||
<ref bean="updateLoanArrearsAgeingJobDetailCronTrigger" />
|
||||
<ref bean="updateLoanPaidInAdvanceJobDetailCronTrigger" />
|
||||
<ref bean="applyAnnualFeeForSavingsJobDetailCronTrigger" />
|
||||
<ref bean="applyHolidaysToJobDetailCronTrigger" />
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
Reference in New Issue
Block a user