Merge pull request #436 from ashok-conflux/MIFOSX-76

MIFOSX-76: Apply holidays to loan repayment schedule
This commit is contained in:
Keith Woodlock
2013-07-08 04:10:16 -07:00
22 changed files with 418 additions and 115 deletions
@@ -0,0 +1 @@
INSERT INTO `c_configuration` (`name`) VALUES ('reschedule-repayments-on-holidays');
@@ -10,4 +10,5 @@ public interface ConfigurationDomainService {
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
boolean isAmazonS3Enabled();
boolean isRescheduleFutureRepaymentsEnabled();
boolean isRescheduleRepaymentsOnHolidaysEnabled();
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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;
@@ -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);
}
@@ -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);
@@ -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;
}
@@ -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);
}
@@ -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);
@@ -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);
}
}
@@ -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,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);
@@ -52,4 +52,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command);
void updateRepaymentsSchedule(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
void applyHolidaysToLoans();
}
@@ -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);
}
}
@@ -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());
}
}
@@ -14,4 +14,6 @@ public interface ScheduledJobRunnerService {
void updateLoanPaidInAdvance();
void applyAnnualFeeForSavings();
void applyHolidaysToLoans();
}
@@ -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>