diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 37ea0963a..fc03c6fed 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -897,11 +897,6 @@ public class SavingsAccount extends AbstractPersistable { } } - /***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 { return transaction; } - public SavingsAccountTransaction addAnnualFee(final DateTimeFormatter formatter, final LocalDate transactionDate) { + public SavingsAccountTransaction addAnnualFee(final DateTimeFormatter formatter, final LocalDate transactionDate, + List existingTransactionIds, List existingReversedTransactionIds) { if (isNotActive()) { @@ -961,6 +957,9 @@ public class SavingsAccount extends AbstractPersistable { 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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java index 51a760d2f..9f09846f2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 9ccbbfacb..0b9adcf74 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -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 existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + 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(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/ApplyAnnualFeeForSavingsScheduledJob.java b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/ApplyAnnualFeeForSavingsScheduledJob.java new file mode 100644 index 000000000..d266bb6fa --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/ApplyAnnualFeeForSavingsScheduledJob.java @@ -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()); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerService.java b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerService.java index 327f48cd9..c335f3913 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerService.java @@ -10,4 +10,6 @@ public interface ScheduledJobRunnerService { void updateLoanSummaryDetails(); void updateLoanArrearsAgeingDetails(); + + void applyAnnualFeeForSavings(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java index 2a4fab657..ccd13de5b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java @@ -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 savingsIds = jdbcTemplate.queryForList(sql, Long.class); + + for (Long savingsId : savingsIds) { + this.savingsAccountWritePlatformService.applyAnnualFee(savingsId); + } + + logger.info("Results affected by update: " + savingsIds.size()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/resources/META-INF/spring/scheduler.xml b/mifosng-provider/src/main/resources/META-INF/spring/scheduler.xml index 4adb9767d..a8af89c57 100644 --- a/mifosng-provider/src/main/resources/META-INF/spring/scheduler.xml +++ b/mifosng-provider/src/main/resources/META-INF/spring/scheduler.xml @@ -1,52 +1,74 @@ - + + 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"> - - - - - - + + + + + + + + + - - - - - - - - - - - - - + + + + + + - - - - - - - - - + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file