Fixed annual fee batch job and savings charges related issues

This commit is contained in:
ashok-conflux
2013-10-05 21:08:25 +05:30
parent 3e3b0d65a3
commit 3181136977
17 changed files with 453 additions and 84 deletions
@@ -293,8 +293,20 @@ public class JsonParserHelper {
final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
final String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) {
final DateTimeFormatter formatter = DateTimeFormat.forPattern(dateFormat).withLocale(clientApplicationLocale);
value = MonthDay.parse(valueAsString.toLowerCase(clientApplicationLocale), formatter);
try {
final DateTimeFormatter formatter = DateTimeFormat.forPattern(dateFormat).withLocale(clientApplicationLocale);
value = MonthDay.parse(valueAsString.toLowerCase(clientApplicationLocale), formatter);
} catch (final IllegalArgumentException e) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.month.day",
"The parameter " + parameterName + " is invalid based on the dateFormat: '" + dateFormat
+ "' and locale: '" + clientApplicationLocale + "' provided:", parameterName, valueAsString,
dateFormat);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
}
@@ -169,6 +169,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
public SavingsAccountChargeData toSavingsAccountChargeData() {
final Long savingsChargeId = null;
final Long savingsAccountId = null;
final BigDecimal amountPaid = BigDecimal.ZERO;
final BigDecimal amountWaived = BigDecimal.ZERO;
final BigDecimal amountWrittenOff = BigDecimal.ZERO;
@@ -179,7 +180,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final LocalDate dueAsOfDate = null;
final MonthDay feeOnMonthDay = null;
return SavingsAccountChargeData.instance(savingsChargeId, this.id, this.name, this.currency, this.amount, amountPaid, amountWaived,
return SavingsAccountChargeData.instance(savingsChargeId, this.id, savingsAccountId, this.name, this.currency, this.amount, amountPaid, amountWaived,
amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, percentage,
amountPercentageAppliedTo, chargeOptions, this.penalty, feeOnMonthDay);
}
@@ -117,7 +117,7 @@ public class Charge extends AbstractPersistable<Long> {
if (!ChargeTimeType.fromInt(getChargeTime()).isWithdrawalFee() && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal");
.failWithCodeNoParameterAddedToErrorCode("savings.charge.calculation.type.percentage.allowed.only.for.withdrawal");
}
} else if (isLoanCharge()) {
@@ -49,7 +49,7 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
public List<EnumOptionData> retrieveCollectionTimeTypes() {
final List<EnumOptionData> chargeTimeTypes = new ArrayList<EnumOptionData>();
for (final ChargeTimeType chargeTimeType : ChargeTimeType.values()) {
if (ChargeTimeType.INVALID.equals(chargeTimeType)) {
if (ChargeTimeType.INVALID.equals(chargeTimeType) || ChargeTimeType.SAVINGS_CLOSURE.equals(chargeTimeType)) {
continue;
}
chargeTimeTypes.add(ChargeEnumerations.chargeTimeType(chargeTimeType));
@@ -81,7 +81,8 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
@Override
public List<EnumOptionData> retrieveSavingsCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE), chargeTimeType(ChargeTimeType.SAVINGS_ACTIVATION),
chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE), chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE),
//chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE),
chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE),
chargeTimeType(ChargeTimeType.ANNUAL_FEE));
}
}
@@ -164,7 +164,7 @@ public class SavingsApiConstants {
interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName,
lockinPeriodFrequencyTypeParamName,
//withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
//withdrawalFeeForTransfersParamName,
withdrawalFeeForTransfersParamName,
feeAmountParamName, feeOnMonthDayParamName, chargesParamName));
/**
@@ -10,15 +10,17 @@ import org.joda.time.LocalDate;
public class SavingsAccountAnnualFeeData {
private final Long id;
private final Long accountId;
private final String accountNo;
private final LocalDate nextAnnualFeeDueDate;
public static SavingsAccountAnnualFeeData instance(final Long id, final String accountNo, final LocalDate nextAnnualFeeDueDate) {
return new SavingsAccountAnnualFeeData(id, accountNo, nextAnnualFeeDueDate);
public static SavingsAccountAnnualFeeData instance(final Long id, final Long accountId, final String accountNo, final LocalDate nextAnnualFeeDueDate) {
return new SavingsAccountAnnualFeeData(id, accountId, accountNo, nextAnnualFeeDueDate);
}
private SavingsAccountAnnualFeeData(final Long id, final String accountNo, final LocalDate nextAnnualFeeDueDate) {
private SavingsAccountAnnualFeeData(final Long id, final Long accountId, final String accountNo, final LocalDate nextAnnualFeeDueDate) {
this.id = id;
this.accountId = accountId;
this.accountNo = accountNo;
this.nextAnnualFeeDueDate = nextAnnualFeeDueDate;
}
@@ -27,11 +29,15 @@ public class SavingsAccountAnnualFeeData {
return this.id;
}
public String getAccountNo() {
return this.accountNo;
public Long getAccountId() {
return this.accountId;
}
public LocalDate getNextAnnualFeeDueDate() {
return this.nextAnnualFeeDueDate;
}
public String getAccountNo() {
return this.accountNo;
}
}
@@ -25,6 +25,9 @@ public class SavingsAccountChargeData {
@SuppressWarnings("unused")
private final Long chargeId;
@SuppressWarnings("unused")
private final Long accountId;
@SuppressWarnings("unused")
private final String name;
@@ -73,6 +76,7 @@ public class SavingsAccountChargeData {
public static SavingsAccountChargeData template(final Collection<ChargeData> chargeOptions) {
final Long id = null;
final Long chargeId = null;
final Long accountId = null;
final String name = null;
final CurrencyData currency = null;
final BigDecimal amount = BigDecimal.ZERO;
@@ -89,29 +93,30 @@ public class SavingsAccountChargeData {
final MonthDay feeOnMonthDay = null;
return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
penalty, feeOnMonthDay);
}
public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final String name, final CurrencyData currency,
public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final Long accountId, final String name, final CurrencyData currency,
final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate,
final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
final Collection<ChargeData> chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay) {
return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
penalty, feeOnMonthDay);
}
private SavingsAccountChargeData(final Long id, final Long chargeId, final String name, final EnumOptionData chargeTimeType,
private SavingsAccountChargeData(final Long id, final Long chargeId, final Long accountId, final String name, final EnumOptionData chargeTimeType,
final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid,
final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding,
final Collection<ChargeData> chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay) {
this.id = id;
this.chargeId = chargeId;
this.accountId = accountId;
this.name = name;
this.chargeTimeType = chargeTimeType;
this.dueDate = dueAsOfDate;
@@ -1212,6 +1212,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
} else if (transactionToUndo.isWaiveCharge()) {
chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
chargeToUndo.updateToPreviousAnnualFeeDueDate();
}
}
}
@@ -1678,6 +1679,9 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final Money amountWaived = savingsAccountCharge.waive(getCurrency());
handleWaiverChargeTransactions(savingsAccountCharge, amountWaived);
//update next annual fee due date
savingsAccountCharge.updateToNextAnnualFeeDueDate();
}
public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition) {
@@ -1733,13 +1737,21 @@ public class SavingsAccount extends AbstractPersistable<Long> {
"multiple.annual.fee.per.account.not.supported");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if(isActive()){
savingsAccountCharge.updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
}else if(isApproved()){
savingsAccountCharge.updateToNextAnnualFeeDueDateFrom(getApprovedOnLocalDate());
}
//if account is in submitted and pending approval state,
//upon account activation annual fee charges due date get updated.
}
//activation charge and withdrawal charges not required this validation
if(savingsAccountCharge.isOnSpecifiedDueDate()){
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate);
}
// add new charge to savings account
charges().add(savingsAccountCharge);
@@ -211,7 +211,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
}
public boolean isPostInterestCalculationRequired(){
return this.isDeposit() || this.isWithdrawal() || this.isPayCharge();
return this.isDeposit() || this.isChargeTransaction();
}
public boolean isInterestPostingAndNotReversed() {
@@ -278,7 +278,14 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
}
public void updateCumulativeBalanceAndDates(final MonetaryCurrency currency, final LocalDate endOfBalanceDate) {
this.balanceEndDate = endOfBalanceDate.toDate();
//balance end date should not be before transaction date
if (endOfBalanceDate != null && endOfBalanceDate.isBefore(this.transactionLocalDate())){
this.balanceEndDate = this.transactionLocalDate().toDate();
}else if(endOfBalanceDate != null){
this.balanceEndDate = endOfBalanceDate.toDate();
}else{
this.balanceEndDate = null;
}
this.balanceNumberOfDays = LocalDateInterval.create(getTransactionLocalDate(), endOfBalanceDate).daysInPeriodInclusiveOfEndDate();
this.cumulativeBalance = Money.of(currency, this.runningBalance).multipliedBy(this.balanceNumberOfDays).getAmount();
}
@@ -374,7 +381,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
Money endOfDayBalance = openingBalance.copy();
if (isDeposit()) {
endOfDayBalance = openingBalance.plus(getAmount(currency));
} else if (isWithdrawal() || isWithdrawalFeeAndNotReversed()) {
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
endOfDayBalance = openingBalance.minus(getAmount(currency));
}
@@ -390,7 +397,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
Money endOfDayBalance = openingBalance.copy();
if (isDeposit()) {
endOfDayBalance = openingBalance.plus(getAmount(currency));
} else if (isWithdrawal() || isWithdrawalFeeAndNotReversed()) {
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
if (openingBalance.isGreaterThanZero()) {
endOfDayBalance = openingBalance.minus(getAmount(currency));
@@ -420,7 +427,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
// if (endOfDayBalance.isLessThanZero()) {
endOfDayBalance = Money.of(currency, this.runningBalance);
// }
} else if (isWithdrawal() || isWithdrawalFeeAndNotReversed()) {
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
// endOfDayBalance = openingBalance.minus(getAmount(currency));
// if (endOfDayBalance.isLessThanZero()) {
endOfDayBalance = Money.of(currency, this.runningBalance);
@@ -471,6 +478,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
return SavingsAccountTransactionType.fromInt(this.typeOf).isChargeTransaction();
}
public boolean isChargeTransactionAndNotReversed(){
return SavingsAccountTransactionType.fromInt(this.typeOf).isChargeTransaction() && isNotReversed();
}
public boolean isWaiveCharge(){
return SavingsAccountTransactionType.fromInt(this.typeOf).isWaiveCharge();
}
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.savings.service;
import java.util.Collection;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
public interface SavingsAccountChargeReadPlatformService {
@@ -17,4 +18,8 @@ public interface SavingsAccountChargeReadPlatformService {
Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(Long savingsAccountId);
SavingsAccountChargeData retrieveSavingsAccountChargeDetails(Long savingsAccountChargeId, Long savingsAccountId);
Collection<SavingsAccountAnnualFeeData> retrieveChargesWithAnnualFeeDue();
Collection<SavingsAccountAnnualFeeData> retrieveChargesWithDue();
}
@@ -19,10 +19,13 @@ import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
import org.mifosplatform.portfolio.charge.service.ChargeDropdownReadPlatformService;
import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -35,6 +38,9 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
//mappers
private final SavingsAccountChargeDueMapper chargeDueMapper;
@Autowired
public SavingsAccountChargeReadPlatformServiceImpl(final PlatformSecurityContext context,
@@ -42,12 +48,13 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
this.context = context;
this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.chargeDueMapper = new SavingsAccountChargeDueMapper();
}
private static final class SavingsAccountChargeMapper implements RowMapper<SavingsAccountChargeData> {
public String schema() {
return "sc.id as id, c.id as chargeId, c.name as name, "
return "sc.id as id, c.id as chargeId, sc.savings_account_id as accountId, c.name as name, "
+ "sc.amount as amountDue, "
+ "sc.amount_paid_derived as amountPaid, "
+ "sc.amount_waived_derived as amountWaived, "
@@ -72,6 +79,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
final Long id = rs.getLong("id");
final Long chargeId = rs.getLong("chargeId");
final Long accountId = rs.getLong("accountId");
final String name = rs.getString("name");
final BigDecimal amount = rs.getBigDecimal("amountDue");
final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid");
@@ -102,16 +110,16 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
if (feeOnDay != null) {
feeOnMonthDay = new MonthDay(feeOnMonth, feeOnDay);
}
final int chargeCalculation = rs.getInt("chargeCalculation");
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
final boolean penalty = rs.getBoolean("penalty");
final Collection<ChargeData> chargeOptions = null;
return SavingsAccountChargeData.instance(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff,
amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo,
chargeOptions, penalty, feeOnMonthDay);
return SavingsAccountChargeData.instance(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived,
amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf,
amountPercentageAppliedTo, chargeOptions, penalty, feeOnMonthDay);
}
}
@@ -161,4 +169,56 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
}
private static final class SavingsAccountChargeDueMapper implements RowMapper<SavingsAccountAnnualFeeData> {
private final String schemaSql;
public SavingsAccountChargeDueMapper() {
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("sac.id as id, ");
sqlBuilder.append("sa.id as accountId, ");
sqlBuilder.append("sa.account_no as accountNo, ");
sqlBuilder.append("sac.charge_due_date as dueDate ");
sqlBuilder.append("from m_savings_account_charge sac join m_savings_account sa on sac.savings_account_id = sa.id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public SavingsAccountAnnualFeeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long accountId = rs.getLong("accountId");
final String accountNo = rs.getString("accountNo");
final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "dueDate");
return SavingsAccountAnnualFeeData.instance(id, accountId, accountNo, annualFeeNextDueDate);
}
}
@Override
public Collection<SavingsAccountAnnualFeeData> retrieveChargesWithAnnualFeeDue() {
final String sql = "select " + this.chargeDueMapper.schema() + " where sac.charge_due_date is not null and sac.charge_time_enum = "
+ ChargeTimeType.ANNUAL_FEE.getValue() + " and sac.charge_due_date <= NOW() and sa.status_enum = "
+ SavingsAccountStatusType.ACTIVE.getValue();
return this.jdbcTemplate.query(sql, this.chargeDueMapper, new Object[] {});
}
@Override
public Collection<SavingsAccountAnnualFeeData> retrieveChargesWithDue() {
final String sql = "select "
+ this.chargeDueMapper.schema()
+ " where sac.charge_due_date is not null and sac.charge_due_date <= NOW() and sac.waived = 0 and sac.is_paid_derived=0 and sac.is_active=1 and sa.status_enum = "
+ SavingsAccountStatusType.ACTIVE.getValue() + " order by sac.charge_due_date ";
return this.jdbcTemplate.query(sql, this.chargeDueMapper, new Object[] {});
}
}
@@ -20,7 +20,7 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult withdrawal(Long savingsId, JsonCommand command);
/*CommandProcessingResult applyAnnualFee(Long savingsId, LocalDate annualFeeTransactionDate);*/
CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, final Long accountId);
CommandProcessingResult calculateInterest(Long savingsId);
@@ -52,5 +52,5 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
void processDueCharges();
void applyChargeDue(final Long savingsAccountChargeId, final Long accountId);
}
@@ -248,36 +248,25 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.build();
}
/*@Transactional
@Transactional
@Override
public CommandProcessingResult applyAnnualFee(final Long savingsId, final LocalDate annualFeeTransactionDate) {
public CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, final Long accountId) {
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
checkClientOrGroupActive(account);
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(
savingsAccountChargeId, accountId);
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final MathContext mc = MathContext.DECIMAL64;
final LocalDate today = DateUtils.getLocalDateOfTenant();
final SavingsAccountTransaction annualFee = account.addAnnualFee(mc, fmt, annualFeeTransactionDate, today);
final Long transactionId = saveTransactionToGenerateTransactionId(annualFee);
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt);
return new CommandProcessingResultBuilder() //
.withEntityId(transactionId) //
.withOfficeId(account.officeId()) //
.withClientId(account.clientId()) //
.withGroupId(account.groupId()) //
.withSavingsId(savingsId) //
.withEntityId(savingsAccountCharge.getId()) //
.withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
.withClientId(savingsAccountCharge.savingsAccount().clientId()) //
.withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
.withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
.build();
}*/
}
@Transactional
@Override
@@ -828,21 +817,16 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
}
@Transactional
@Override
@CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES)
public void processDueCharges() {
public void applyChargeDue(final Long savingsAccountChargeId, final Long accountId) {
// always use current date as transaction date for batch job
final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
final List<SavingsAccountCharge> pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate());
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(
savingsAccountChargeId, accountId);
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
for (SavingsAccountCharge savingsAccountCharge : pendingCharges) {
try {
payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt);
} catch (Exception exception) {
// TODO: AA - provide a meaningful error with failed
// transactions list
}
}
payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt);
}
@Transactional
@@ -857,7 +841,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter);
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today);
} else {
@@ -14,4 +14,6 @@ public interface ScheduledJobRunnerService {
void updateLoanPaidInAdvance();
void applyAnnualFeeForSavings();
void applyDueChargesForSavings();
}
@@ -5,11 +5,17 @@
*/
package org.mifosplatform.scheduledjobs.service;
import java.util.Collection;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.service.RoutingDataSourceServiceFactory;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.jobs.annotation.CronTarget;
import org.mifosplatform.infrastructure.jobs.service.JobName;
import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,18 +30,16 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
private final static Logger logger = LoggerFactory.getLogger(ScheduledJobRunnerServiceImpl.class);
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
@SuppressWarnings("unused")
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
@SuppressWarnings("unused")
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
@Autowired
public ScheduledJobRunnerServiceImpl(final RoutingDataSourceServiceFactory dataSourceServiceFactory,
final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
final SavingsAccountReadPlatformService savingsAccountReadPlatformService) {
final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService) {
this.dataSourceServiceFactory = dataSourceServiceFactory;
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
}
@Transactional
@@ -194,27 +198,50 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result);
}
@Transactional
@Override
@CronTarget(jobName = JobName.APPLY_ANNUAL_FEE_FOR_SAVINGS)
public void applyAnnualFeeForSavings() {
/*final Collection<SavingsAccountAnnualFeeData> annualFeeData = this.savingsAccountReadPlatformService
.retrieveAccountsWithAnnualFeeDue();
final Collection<SavingsAccountAnnualFeeData> annualFeeData = this.savingsAccountChargeReadPlatformService
.retrieveChargesWithAnnualFeeDue();
for (final SavingsAccountAnnualFeeData savingsAccountReference : annualFeeData) {
try {
this.savingsAccountWritePlatformService.applyAnnualFee(savingsAccountReference.getId(),
savingsAccountReference.getNextAnnualFeeDueDate());
savingsAccountReference.getAccountId());
} catch (final PlatformApiDataValidationException e) {
final List<ApiParameterError> errors = e.getErrors();
for (final ApiParameterError error : errors) {
logger.error("Apply annual fee failed for account:" + savingsAccountReference.getAccountNo() + " with message "
+ error.getDeveloperMessage());
}
} catch (final Exception ex){
//need to handle this scenario
}
}
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + annualFeeData.size());*/
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + annualFeeData.size());
}
@Override
@CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES)
public void applyDueChargesForSavings() {
final Collection<SavingsAccountAnnualFeeData> chargesDueData = this.savingsAccountChargeReadPlatformService
.retrieveChargesWithDue();
for (final SavingsAccountAnnualFeeData savingsAccountReference : chargesDueData) {
try {
this.savingsAccountWritePlatformService.applyChargeDue(savingsAccountReference.getId(), savingsAccountReference.getAccountId());
} catch (final PlatformApiDataValidationException e) {
final List<ApiParameterError> errors = e.getErrors();
for (final ApiParameterError error : errors) {
logger.error("Apply Charges due for savings failed for account:" + savingsAccountReference.getAccountNo() + " with message "
+ error.getDeveloperMessage());
}
}
}
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + chargesDueData.size());
}
}