Merge pull request #313 from vishwasbabu/wuyanna-master

Apply annual fees for savings accounts
This commit is contained in:
Vishwas Babu A J
2013-05-27 09:20:51 -07:00
7 changed files with 203 additions and 77 deletions
@@ -897,11 +897,6 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
}
/***TODO Fixme: Annual fee seems to be added for all withdrawals?**/
final Money annualFee = Money.of(this.currency, this.annualFeeAmount);
SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, transactionDate, annualFee);
this.transactions.add(annualFeeTransaction);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
final LocalDate today = DateUtils.getLocalDateOfTenant();
@@ -910,7 +905,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return transaction;
}
public SavingsAccountTransaction addAnnualFee(final DateTimeFormatter formatter, final LocalDate transactionDate) {
public SavingsAccountTransaction addAnnualFee(final DateTimeFormatter formatter, final LocalDate transactionDate,
List<Long> existingTransactionIds, List<Long> existingReversedTransactionIds) {
if (isNotActive()) {
@@ -961,6 +957,9 @@ public class SavingsAccount extends AbstractPersistable<Long> {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final Money annualFee = Money.of(this.currency, this.annualFeeAmount);
SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, transactionDate, annualFee);
this.transactions.add(annualFeeTransaction);
@@ -21,6 +21,8 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult deposit(Long savingsId, JsonCommand command);
CommandProcessingResult withdrawal(Long savingsId, JsonCommand command);
CommandProcessingResult applyAnnualFee(Long savingsId);
CommandProcessingResult calculateInterest(Long savingsId, JsonCommand command);
@@ -359,6 +359,35 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.build();
}
@Transactional
@Override
public CommandProcessingResult applyAnnualFee(final Long savingsId) {
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId);
final LocalDate transactionDate = LocalDate.now();
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccountTransaction annualFee = account.addAnnualFee(fmt, transactionDate, existingTransactionIds,
existingReversedTransactionIds);
final Long transactionId = saveTransactionToGenerateTransactionId(annualFee);
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() //
.withEntityId(transactionId) //
.withOfficeId(account.officeId()) //
.withClientId(account.clientId()) //
.withGroupId(account.groupId()) //
.withSavingsId(savingsId) //
.build();
}
@Override
public CommandProcessingResult calculateInterest(final Long savingsId, final JsonCommand command) {
this.context.authenticatedUser();
@@ -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 ApplyAnnualFeeForSavingsScheduledJob {
private final static Logger logger = LoggerFactory.getLogger(ApplyAnnualFeeForSavingsScheduledJob.class);
private final ScheduledJobRunnerService scheduledJobRunnerService;
public ApplyAnnualFeeForSavingsScheduledJob(final ScheduledJobRunnerService scheduledJobRunnerService) {
this.scheduledJobRunnerService = scheduledJobRunnerService;
}
public void execute() {
logger.info("Running Job ApplyAnnualFeeForSavingsScheduledJob " + new Date());
this.scheduledJobRunnerService.applyAnnualFeeForSavings();
logger.info("Finishing Job ApplyAnnualFeeForSavingsScheduledJob " + new Date());
}
}
@@ -10,4 +10,6 @@ public interface ScheduledJobRunnerService {
void updateLoanSummaryDetails();
void updateLoanArrearsAgeingDetails();
void applyAnnualFeeForSavings();
}
@@ -5,10 +5,13 @@
*/
package org.mifosplatform.scheduledjobs.service;
import java.util.List;
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.savings.service.SavingsAccountWritePlatformService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -23,18 +26,20 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
private final TenantDetailsService tenantDetailsService;
private final DataSourcePerTenantService dataSourcePerTenantService;
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
@Autowired
public ScheduledJobRunnerServiceImpl(final DataSourcePerTenantService dataSourcePerTenantService,
final TenantDetailsService tenantDetailsService) {
final TenantDetailsService tenantDetailsService, final SavingsAccountWritePlatformService savingsAccountWritePlatformService) {
this.dataSourcePerTenantService = dataSourcePerTenantService;
this.tenantDetailsService = tenantDetailsService;
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
}
@Transactional
@Override
public void updateLoanSummaryDetails() {
final MifosPlatformTenant tenant = this.tenantDetailsService.loadTenantById("default");
ThreadLocalContextUtil.setTenant(tenant);
@@ -65,38 +70,51 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
updateSqlBuilder.append("WHERE ml.disbursedon_date is not null ");
updateSqlBuilder.append("GROUP BY ml.id ");
updateSqlBuilder.append(") x on x.loanId = m_loan.id ");
updateSqlBuilder.append("SET m_loan.principal_disbursed_derived = x.principal_disbursed_derived,");
updateSqlBuilder.append("m_loan.principal_repaid_derived = x.principal_repaid_derived,");
updateSqlBuilder.append("m_loan.principal_writtenoff_derived = x.principal_writtenoff_derived,");
updateSqlBuilder.append("m_loan.principal_outstanding_derived = (x.principal_disbursed_derived - (x.principal_repaid_derived + x.principal_writtenoff_derived)),");
updateSqlBuilder
.append("m_loan.principal_outstanding_derived = (x.principal_disbursed_derived - (x.principal_repaid_derived + x.principal_writtenoff_derived)),");
updateSqlBuilder.append("m_loan.interest_charged_derived = x.interest_charged_derived,");
updateSqlBuilder.append("m_loan.interest_repaid_derived = x.interest_repaid_derived,");
updateSqlBuilder.append("m_loan.interest_waived_derived = x.interest_waived_derived,");
updateSqlBuilder.append("m_loan.interest_writtenoff_derived = x.interest_writtenoff_derived,");
updateSqlBuilder.append("m_loan.interest_outstanding_derived = (x.interest_charged_derived - (x.interest_repaid_derived + x.interest_waived_derived + x.interest_writtenoff_derived)),");
updateSqlBuilder
.append("m_loan.interest_outstanding_derived = (x.interest_charged_derived - (x.interest_repaid_derived + x.interest_waived_derived + x.interest_writtenoff_derived)),");
updateSqlBuilder.append("m_loan.fee_charges_charged_derived = x.fee_charges_charged_derived,");
updateSqlBuilder.append("m_loan.fee_charges_repaid_derived = x.fee_charges_repaid_derived,");
updateSqlBuilder.append("m_loan.fee_charges_waived_derived = x.fee_charges_waived_derived,");
updateSqlBuilder.append("m_loan.fee_charges_writtenoff_derived = x.fee_charges_writtenoff_derived,");
updateSqlBuilder.append("m_loan.fee_charges_outstanding_derived = (x.fee_charges_charged_derived - (x.fee_charges_repaid_derived + x.fee_charges_waived_derived + x.fee_charges_writtenoff_derived)),");
updateSqlBuilder
.append("m_loan.fee_charges_outstanding_derived = (x.fee_charges_charged_derived - (x.fee_charges_repaid_derived + x.fee_charges_waived_derived + x.fee_charges_writtenoff_derived)),");
updateSqlBuilder.append("m_loan.penalty_charges_charged_derived = x.penalty_charges_charged_derived,");
updateSqlBuilder.append("m_loan.penalty_charges_repaid_derived = x.penalty_charges_repaid_derived,");
updateSqlBuilder.append("m_loan.penalty_charges_waived_derived = x.penalty_charges_waived_derived,");
updateSqlBuilder.append("m_loan.penalty_charges_writtenoff_derived = x.penalty_charges_writtenoff_derived,");
updateSqlBuilder.append("m_loan.penalty_charges_outstanding_derived = (x.penalty_charges_charged_derived - (x.penalty_charges_repaid_derived + x.penalty_charges_waived_derived + x.penalty_charges_writtenoff_derived)),");
updateSqlBuilder.append("m_loan.total_expected_repayment_derived = (x.principal_disbursed_derived + x.interest_charged_derived + x.fee_charges_charged_derived + x.penalty_charges_charged_derived),");
updateSqlBuilder.append("m_loan.total_repayment_derived = (x.principal_repaid_derived + x.interest_repaid_derived + x.fee_charges_repaid_derived + x.penalty_charges_repaid_derived),");
updateSqlBuilder.append("m_loan.total_expected_costofloan_derived = (x.interest_charged_derived + x.fee_charges_charged_derived + x.penalty_charges_charged_derived),");
updateSqlBuilder.append("m_loan.total_costofloan_derived = (x.interest_repaid_derived + x.fee_charges_repaid_derived + x.penalty_charges_repaid_derived),");
updateSqlBuilder.append("m_loan.total_waived_derived = (x.interest_waived_derived + x.fee_charges_waived_derived + x.penalty_charges_waived_derived),");
updateSqlBuilder.append("m_loan.total_writtenoff_derived = (x.interest_writtenoff_derived + x.fee_charges_writtenoff_derived + x.penalty_charges_writtenoff_derived),");
updateSqlBuilder
.append("m_loan.penalty_charges_outstanding_derived = (x.penalty_charges_charged_derived - (x.penalty_charges_repaid_derived + x.penalty_charges_waived_derived + x.penalty_charges_writtenoff_derived)),");
updateSqlBuilder
.append("m_loan.total_expected_repayment_derived = (x.principal_disbursed_derived + x.interest_charged_derived + x.fee_charges_charged_derived + x.penalty_charges_charged_derived),");
updateSqlBuilder
.append("m_loan.total_repayment_derived = (x.principal_repaid_derived + x.interest_repaid_derived + x.fee_charges_repaid_derived + x.penalty_charges_repaid_derived),");
updateSqlBuilder
.append("m_loan.total_expected_costofloan_derived = (x.interest_charged_derived + x.fee_charges_charged_derived + x.penalty_charges_charged_derived),");
updateSqlBuilder
.append("m_loan.total_costofloan_derived = (x.interest_repaid_derived + x.fee_charges_repaid_derived + x.penalty_charges_repaid_derived),");
updateSqlBuilder
.append("m_loan.total_waived_derived = (x.interest_waived_derived + x.fee_charges_waived_derived + x.penalty_charges_waived_derived),");
updateSqlBuilder
.append("m_loan.total_writtenoff_derived = (x.interest_writtenoff_derived + x.fee_charges_writtenoff_derived + x.penalty_charges_writtenoff_derived),");
updateSqlBuilder.append("m_loan.total_outstanding_derived=");
updateSqlBuilder.append(" (x.principal_disbursed_derived - (x.principal_repaid_derived + x.principal_writtenoff_derived)) + ");
updateSqlBuilder.append(" (x.interest_charged_derived - (x.interest_repaid_derived + x.interest_waived_derived + x.interest_writtenoff_derived)) +");
updateSqlBuilder.append(" (x.fee_charges_charged_derived - (x.fee_charges_repaid_derived + x.fee_charges_waived_derived + x.fee_charges_writtenoff_derived)) +");
updateSqlBuilder.append(" (x.penalty_charges_charged_derived - (x.penalty_charges_repaid_derived + x.penalty_charges_waived_derived + x.penalty_charges_writtenoff_derived))");
updateSqlBuilder
.append(" (x.interest_charged_derived - (x.interest_repaid_derived + x.interest_waived_derived + x.interest_writtenoff_derived)) +");
updateSqlBuilder
.append(" (x.fee_charges_charged_derived - (x.fee_charges_repaid_derived + x.fee_charges_waived_derived + x.fee_charges_writtenoff_derived)) +");
updateSqlBuilder
.append(" (x.penalty_charges_charged_derived - (x.penalty_charges_repaid_derived + x.penalty_charges_waived_derived + x.penalty_charges_writtenoff_derived))");
int result = jdbcTemplate.update(updateSqlBuilder.toString());
logger.info("Results affected by update: " + result);
@@ -112,19 +130,24 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
jdbcTemplate.execute("truncate table m_loan_arrears_aging");
StringBuilder updateSqlBuilder = new StringBuilder(900);
updateSqlBuilder.append("INSERT INTO m_loan_arrears_aging(`loan_id`,`principal_overdue_derived`,`interest_overdue_derived`,`fee_charges_overdue_derived`,`penalty_charges_overdue_derived`,`total_overdue_derived`,`overdue_since_date_derived`)");
updateSqlBuilder
.append("INSERT INTO m_loan_arrears_aging(`loan_id`,`principal_overdue_derived`,`interest_overdue_derived`,`fee_charges_overdue_derived`,`penalty_charges_overdue_derived`,`total_overdue_derived`,`overdue_since_date_derived`)");
updateSqlBuilder.append("select ml.id as loanId,");
updateSqlBuilder.append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) as principal_overdue_derived,");
updateSqlBuilder.append("SUM((ifnull(mr.interest_amount,0) - ifnull(mr.interest_completed_derived, 0))) as interest_overdue_derived,");
updateSqlBuilder.append("SUM((ifnull(mr.fee_charges_amount,0) - ifnull(mr.fee_charges_completed_derived, 0))) as fee_charges_overdue_derived,");
updateSqlBuilder.append("SUM((ifnull(mr.penalty_charges_amount,0) - ifnull(mr.penalty_charges_completed_derived, 0))) as penalty_charges_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) as principal_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.interest_amount,0) - ifnull(mr.interest_completed_derived, 0))) as interest_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.fee_charges_amount,0) - ifnull(mr.fee_charges_completed_derived, 0))) as fee_charges_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.penalty_charges_amount,0) - ifnull(mr.penalty_charges_completed_derived, 0))) as penalty_charges_overdue_derived,");
updateSqlBuilder.append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) +");
updateSqlBuilder.append("SUM((ifnull(mr.interest_amount,0) - ifnull(mr.interest_completed_derived, 0))) +");
updateSqlBuilder.append("SUM((ifnull(mr.fee_charges_amount,0) - ifnull(mr.fee_charges_completed_derived, 0))) +");
updateSqlBuilder.append("SUM((ifnull(mr.penalty_charges_amount,0) - ifnull(mr.penalty_charges_completed_derived, 0))) as total_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.penalty_charges_amount,0) - ifnull(mr.penalty_charges_completed_derived, 0))) as total_overdue_derived,");
updateSqlBuilder.append("MIN(mr.duedate) as overdue_since_date_derived ");
updateSqlBuilder.append(" FROM m_loan ml ");
updateSqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
@@ -132,9 +155,27 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
updateSqlBuilder.append(" and mr.completed_derived is false ");
updateSqlBuilder.append(" and mr.duedate < CURDATE() ");
updateSqlBuilder.append(" GROUP BY ml.id");
int result = jdbcTemplate.update(updateSqlBuilder.toString());
logger.info("Results affected by update: " + result);
}
@Override
public void applyAnnualFeeForSavings() {
final MifosPlatformTenant tenant = this.tenantDetailsService.loadTenantById("default");
ThreadLocalContextUtil.setTenant(tenant);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourcePerTenantService.retrieveTenantAwareDataSource());
String sql = "SELECT ms.id as savingsAccountId FROM m_savings_account ms WHERE ms.annual_fee_next_due_date <= NOW() ";
List<Long> savingsIds = jdbcTemplate.queryForList(sql, Long.class);
for (Long savingsId : savingsIds) {
this.savingsAccountWritePlatformService.applyAnnualFee(savingsId);
}
logger.info("Results affected by update: " + savingsIds.size());
}
}
@@ -1,52 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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/.
-->
<!-- 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/. -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="updateLoanSummariesScheduledJob" class="org.mifosplatform.scheduledjobs.UpdateLoanSummariesScheduledJob">
<constructor-arg ref="scheduledJobRunnerService" />
</bean>
<bean id="updateLoanArrearsAgeingScheduledJob" class="org.mifosplatform.scheduledjobs.UpdateLoanArrearsAgeingScheduledJob">
<constructor-arg ref="scheduledJobRunnerService" />
</bean>
<bean id="updateLoanSummariesScheduledJob"
class="org.mifosplatform.scheduledjobs.UpdateLoanSummariesScheduledJob">
<constructor-arg ref="scheduledJobRunnerService" />
</bean>
<bean id="updateLoanArrearsAgeingScheduledJob"
class="org.mifosplatform.scheduledjobs.UpdateLoanArrearsAgeingScheduledJob">
<constructor-arg ref="scheduledJobRunnerService" />
</bean>
<bean id="applyAnnualFeeForSavingsScheduledJob"
class="org.mifosplatform.scheduledjobs.ApplyAnnualFeeForSavingsScheduledJob">
<constructor-arg ref="scheduledJobRunnerService" />
</bean>
<bean id="updateLoanSummariesJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="group" value="mifosJobs" />
<property name="targetObject" ref="updateLoanSummariesScheduledJob" />
<property name="targetMethod" value="execute" />
<property name="concurrent" value="false" />
</bean>
<bean id="UpdateLoanArrearsAgeingJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="group" value="mifosJobs" />
<property name="targetObject" ref="updateLoanArrearsAgeingScheduledJob" />
<property name="targetMethod" value="execute" />
<property name="concurrent" value="false" />
</bean>
<bean id="updateLoanSummariesJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="group" value="mifosJobs" />
<property name="targetObject" ref="updateLoanSummariesScheduledJob" />
<property name="targetMethod" value="execute" />
<property name="concurrent" value="false" />
</bean>
<bean id="updateLoanSummariesJobCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="updateLoanSummariesJobDetail" />
<property name="cronExpression" value="0 0 22 1/1 * ? *" />
</bean>
<bean id="UpdateLoanArrearsAgeingJobDetailCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="UpdateLoanArrearsAgeingJobDetail" />
<property name="cronExpression" value="0 10 22 1/1 * ? *" />
</bean>
<bean id="updateLoanArrearsAgeingJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="group" value="mifosJobs" />
<property name="targetObject" ref="updateLoanArrearsAgeingScheduledJob" />
<property name="targetMethod" value="execute" />
<property name="concurrent" value="false" />
</bean>
<bean name="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="updateLoanSummariesJobCronTrigger" />
<ref bean="UpdateLoanArrearsAgeingJobDetailCronTrigger" />
</list>
</property>
</bean>
<bean id="applyAnnualFeeForSavingsJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="group" value="mifosJobs" />
<property name="targetObject" ref="applyAnnualFeeForSavingsScheduledJob" />
<property name="targetMethod" value="execute" />
<property name="concurrent" value="false" />
</bean>
<bean id="updateLoanSummariesJobCronTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="updateLoanSummariesJobDetail" />
<property name="cronExpression" value="0 0 22 1/1 * ? *" />
</bean>
<bean id="updateLoanArrearsAgeingJobDetailCronTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="updateLoanArrearsAgeingJobDetail" />
<property name="cronExpression" value="0 10 22 1/1 * ? *" />
</bean>
<bean id="applyAnnualFeeForSavingsJobDetailCronTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="applyAnnualFeeForSavingsJobDetail" />
<property name="cronExpression" value="0 20 22 1/1 * ? *" />
</bean>
<bean name="schedulerFactory"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="updateLoanSummariesJobCronTrigger" />
<ref bean="updateLoanArrearsAgeingJobDetailCronTrigger" />
<ref bean="applyAnnualFeeForSavingsJobDetailCronTrigger" />
</list>
</property>
</bean>
</beans>