added support for undo savings fees

This commit is contained in:
ashok-conflux
2013-09-25 19:35:42 +05:30
parent 617662f0df
commit aabe9549e0
10 changed files with 176 additions and 151 deletions
@@ -63,4 +63,8 @@ public class DateUtils {
final String formattedSqlDate = df.format(date); final String formattedSqlDate = df.format(date);
return formattedSqlDate; return formattedSqlDate;
} }
public static boolean isInFuture(final LocalDate date){
return date.isAfter(getLocalDateOfTenant());
}
} }
@@ -19,7 +19,7 @@ public enum SavingsAccountTransactionType {
INTEREST_POSTING(3, "savingsAccountTransactionType.interestPosting"), // INTEREST_POSTING(3, "savingsAccountTransactionType.interestPosting"), //
WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"), // WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"), //
ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee"), // ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee"), //
WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharges"), // WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharge"), //
PAY_CHARGE(7, "savingsAccountTransactionType.payCharge"), // PAY_CHARGE(7, "savingsAccountTransactionType.payCharge"), //
INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), // INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), //
APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), // APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), //
@@ -34,6 +34,7 @@ public class SavingsApiConstants {
public static String adjustTransactionAction = ".adjusttransaction"; public static String adjustTransactionAction = ".adjusttransaction";
public static String closeAction = ".close"; public static String closeAction = ".close";
public static String payChargeTransactionAction = ".paycharge"; public static String payChargeTransactionAction = ".paycharge";
public static String waiveChargeTransactionAction = ".waivecharge";
// command // command
public static String COMMAND_UNDO_TRANSACTION = "undo"; public static String COMMAND_UNDO_TRANSACTION = "undo";
@@ -8,13 +8,13 @@ package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
@@ -1497,15 +1497,18 @@ public class SavingsAccount extends AbstractPersistable<Long> {
.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay).plusYears(1); .withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay).plusYears(1);
this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate(); this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate();
} }
} else if (transactionToUndo.isCharge()) { } else if (transactionToUndo.isCharge() || transactionToUndo.isWaiveCharge()) {
// undo charge // undo charge
final Set<SavingsAccountChargePaidBy> chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid(); final Set<SavingsAccountChargePaidBy> chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid();
for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) { for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) {
final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge(); final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge();
chargeToUndo.resetPaidAmount(this.currency); if(transactionToUndo.isCharge()){
chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
}else if (transactionToUndo.isWaiveCharge()){
chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
}
} }
} }
} }
} }
@@ -1899,6 +1902,20 @@ public class SavingsAccount extends AbstractPersistable<Long> {
} }
public void removeCharge(final SavingsAccountCharge charge) { public void removeCharge(final SavingsAccountCharge charge) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
if (isClosed()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.closed");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
if (isActive() || isApproved()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.active");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.charges.remove(charge); this.charges.remove(charge);
} }
@@ -1936,15 +1953,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
// waive charge // waive charge
final Money amountWaived = savingsAccountCharge.waive(getCurrency()); final Money amountWaived = savingsAccountCharge.waive(getCurrency());
// persist an entry in transaction details handleWaiverChargeTransactions(savingsAccountCharge, amountWaived);
final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(),
savingsAccountCharge.getDueLocalDate(), amountWaived);
this.transactions.add(chargeTransaction);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
final MathContext mc = MathContext.DECIMAL64;
calculateInterestUsing(mc, savingsAccountCharge.getDueLocalDate());
} }
public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition) { public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition) {
@@ -1966,10 +1975,16 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate(); final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate();
if (chargeDueDate != null && chargeDueDate.isBefore(getActivationLocalDate())) { if (savingsAccountCharge.isOnSpecifiedDueDate()){
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) if(getActivationLocalDate() != null && chargeDueDate.isBefore(getActivationLocalDate())){
.failWithCodeNoParameterAddedToErrorCode("before.activationDate"); baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter))
throw new PlatformApiDataValidationException(dataValidationErrors); .failWithCodeNoParameterAddedToErrorCode("before.activationDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}else if(getSubmittedOnLocalDate() != null && chargeDueDate.isBefore(getSubmittedOnLocalDate())){
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("before.submittedOnDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
} }
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate); validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate);
@@ -1979,7 +1994,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
} }
public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final LocalDate transactionDate, public void payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, final LocalDate transactionDate,
final DateTimeFormatter formatter, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) { final DateTimeFormatter formatter, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
@@ -1996,13 +2011,23 @@ public class SavingsAccount extends AbstractPersistable<Long> {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
if (transactionDate != null && transactionDate.isBefore(getActivationLocalDate())) { if (savingsAccountCharge.isOnSpecifiedDueDate()){
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) if(getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())){
.failWithCodeNoParameterAddedToErrorCode("before.activationDate"); baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter))
throw new PlatformApiDataValidationException(dataValidationErrors); .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}else if(getSubmittedOnLocalDate() != null && transactionDate.isBefore(getSubmittedOnLocalDate())){
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("transaction.before.submittedOnDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
} }
final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId); if (DateUtils.isInFuture(transactionDate)){
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
// validate charge is not already paid or waived // validate charge is not already paid or waived
if (savingsAccountCharge.isWaived()) { if (savingsAccountCharge.isWaived()) {
@@ -2014,7 +2039,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
} }
final Money amountPaidMoney = Money.of(currency, amountPaid); final Money amountPaidMoney = Money.of(currency, amountPaid);
if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(amountPaidMoney)){ if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(amountPaidMoney)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access"); baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
@@ -2022,59 +2047,31 @@ public class SavingsAccount extends AbstractPersistable<Long> {
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final Money chargePaid = savingsAccountCharge.pay(getCurrency(), amountPaidMoney); savingsAccountCharge.pay(getCurrency(), amountPaidMoney);
// persist transaction details
final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, chargePaid);
final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge,
amountPaid);
chargeTransaction.getSavingsAccountChargesPaid().add(chargePaidBy);
this.transactions.add(chargeTransaction);
final MathContext mc = MathContext.DECIMAL64;
if (isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
calculateInterestUsing(mc, today);
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.payChargeTransactionAction);
handlePayChargeTransactions(savingsAccountCharge, amountPaidMoney, transactionDate);
} }
public void payDueCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final List<Long> existingTransactionIds, private void handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount,
final List<Long> existingReversedTransactionIds) { final LocalDate transactionDate) {
final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate,
transactionAmount);
handleChargeTransactions(savingsAccountCharge, chargeTransaction);
}
existingTransactionIds.addAll(findExistingTransactionIds()); private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount) {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.waiver(this, office(),
final Money amountPaid = savingsAccountCharge.payDueCharge(getCurrency()); DateUtils.getLocalDateOfTenant(), transactionAmount);
handleChargeTransactions(savingsAccountCharge, chargeTransaction);
}
// persist transaction details private void handleChargeTransactions(final SavingsAccountCharge savingsAccountCharge, final SavingsAccountTransaction transaction) {
final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, amountPaid); // Provide a link between transaction and savings charge for which
// amount is waived.
final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(transaction, savingsAccountCharge, transaction
amountPaid.getAmount()); .getAmount(this.getCurrency()).getAmount());
transaction.getSavingsAccountChargesPaid().add(chargePaidBy);
chargeTransaction.getSavingsAccountChargesPaid().add(chargePaidBy); this.getTransactions().add(transaction);
this.transactions.add(chargeTransaction);
final MathContext mc = MathContext.DECIMAL64;
if (isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
calculateInterestUsing(mc, today);
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
validateAccountBalanceDoesNotBecomeNegative(amountPaid.getAmount());
} }
private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) { private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) {
@@ -214,8 +214,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.waived = false; this.waived = false;
} }
public void resetPaidAmount(final MonetaryCurrency currency) { public void undoPayment(final MonetaryCurrency currency, final Money transactionAmount) {
this.amountPaid = BigDecimal.ZERO; Money amountPaid = getAmountPaid(currency);
amountPaid = amountPaid.minus(transactionAmount);
this.amountPaid = amountPaid.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency); this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false; this.paid = false;
} }
@@ -223,11 +225,18 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
public Money waive(final MonetaryCurrency currency) { public Money waive(final MonetaryCurrency currency) {
this.amountWaived = this.amountOutstanding; this.amountWaived = this.amountOutstanding;
this.amountOutstanding = BigDecimal.ZERO; this.amountOutstanding = BigDecimal.ZERO;
this.paid = false;
this.waived = true; this.waived = true;
return getAmountWaived(currency); return getAmountWaived(currency);
} }
public void undoWaiver(final MonetaryCurrency currency, final Money transactionAmount) {
Money amountWaived = getAmountWaived(currency);
amountWaived = amountWaived.minus(transactionAmount);
this.amountWaived = amountWaived.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency);
this.waived = false;
}
public Money pay(final MonetaryCurrency currency, final Money amountPaid) { public Money pay(final MonetaryCurrency currency, final Money amountPaid) {
Money amountPaidToDate = Money.of(currency, this.amountPaid); Money amountPaidToDate = Money.of(currency, this.amountPaid);
@@ -237,20 +246,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.amountPaid = amountPaidToDate.getAmount(); this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = amountOutstanding.getAmount(); this.amountOutstanding = amountOutstanding.getAmount();
this.paid = determineIfFullyPaid(); this.paid = determineIfFullyPaid();
return amountPaid; return amountOutstanding;
}
public Money payDueCharge(final MonetaryCurrency currency) {
Money amountPaidToDate = Money.of(currency, this.amountPaid);
Money amountOutstanding = Money.of(currency, this.amountOutstanding);
final Money amountPaid = Money.of(currency, this.amountOutstanding);;
amountPaidToDate = amountPaidToDate.plus(amountPaid);
amountOutstanding = amountOutstanding.minus(amountPaid);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = amountOutstanding.getAmount();
this.paid = determineIfFullyPaid();
return amountPaid;
} }
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) { private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
@@ -410,6 +406,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return this.amount; return this.amount;
} }
public BigDecimal amoutOutstanding(){
return this.amountOutstanding;
}
public boolean isFeeCharge() { public boolean isFeeCharge() {
return !this.penaltyCharge; return !this.penaltyCharge;
} }
@@ -18,6 +18,6 @@ public interface SavingsAccountChargeRepository extends JpaRepository<SavingsAcc
SavingsAccountCharge findByIdAndSavingsAccountId(Long id, Long savingsAccountId); SavingsAccountCharge findByIdAndSavingsAccountId(Long id, Long savingsAccountId);
@Query("from SavingsAccountCharge sac where sac.dueDate <=:transactionDate and sac.waived = 0 and sac.paid=0") @Query("from SavingsAccountCharge sac where sac.dueDate <=:transactionDate and sac.waived = 0 and sac.paid=0 order by sac.dueDate")
List<SavingsAccountCharge> findPendingCharges(@Param("transactionDate") Date transactionDate); List<SavingsAccountCharge> findPendingCharges(@Param("transactionDate") Date transactionDate);
} }
@@ -5,7 +5,6 @@
*/ */
package org.mifosplatform.portfolio.savings.domain; package org.mifosplatform.portfolio.savings.domain;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -210,6 +210,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawal(); return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawal();
} }
public boolean isPostInterestCalculationRequired(){
return this.isDeposit() || this.isWithdrawal() || this.isCharge();
}
public boolean isInterestPostingAndNotReversed() { public boolean isInterestPostingAndNotReversed() {
return SavingsAccountTransactionType.fromInt(this.typeOf).isInterestPosting() && isNotReversed(); return SavingsAccountTransactionType.fromInt(this.typeOf).isInterestPosting() && isNotReversed();
} }
@@ -52,5 +52,5 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
void payDueCharges(); void processDueCharges();
} }
@@ -353,7 +353,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
} }
account.undoTransaction(transactionId, reversedTransactionIds); account.undoTransaction(transactionId, reversedTransactionIds);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
if ((savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal()) if (savingsAccountTransaction.isPostInterestCalculationRequired()
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
account.postInterest(mc, today, newTransactionIds, reversedTransactionIds); account.postInterest(mc, today, newTransactionIds, reversedTransactionIds);
} else { } else {
@@ -606,7 +606,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount); checkClientOrGroupActive(savingsAccount);
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final Long chargeDefinitionId = command.longValueOfParameterNamed(chargeIdParamName); final Long chargeDefinitionId = command.longValueOfParameterNamed(chargeIdParamName);
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
@@ -635,12 +636,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingAccountRepository.saveAndFlush(savingsAccount); this.savingAccountRepository.saveAndFlush(savingsAccount);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsAccountCharge.getId()) // .withEntityId(savingsAccountCharge.getId()) //
.withOfficeId(savingsAccount.officeId()) // .withOfficeId(savingsAccount.officeId()) //
.withClientId(savingsAccount.clientId()) // .withClientId(savingsAccount.clientId()) //
.withGroupId(savingsAccount.groupId()) // .withGroupId(savingsAccount.groupId()) //
.withLoanId(savingsAccountId) // .withSavingsId(savingsAccountId) //
.build(); .build();
} }
@@ -688,11 +688,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge); this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withEntityId(savingsChargeId) // .withEntityId(savingsAccountCharge.getId()) //
// .withOfficeId(savings) // .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
// .withClientId(loan.getClientId()) // .withClientId(savingsAccountCharge.savingsAccount().clientId()) //
// .withGroupId(loan.getGroupId()) // .withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
// .withLoanId(savingsAccountId) // .withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
.with(changes) // .with(changes) //
.build(); .build();
} }
@@ -703,21 +703,36 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(
savingsAccountChargeId, savingsAccountId);
// Get Savings account from savings charge
final SavingsAccount account = savingsAccountCharge.savingsAccount();
this.savingAccountAssembler.assignSavingAccountHelpers(account);
final List<Long> existingTransactionIds = new ArrayList<Long>(); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); account.waiveCharge(savingsAccountChargeId, existingTransactionIds, existingReversedTransactionIds);
checkClientOrGroupActive(savingsAccount);
savingsAccount.waiveCharge(savingsAccountChargeId, existingTransactionIds, existingReversedTransactionIds); final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
this.savingAccountRepository.saveAndFlush(savingsAccount); account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction);
this.savingAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withEntityId(savingsAccountChargeId) // .withEntityId(savingsAccountChargeId) //
.withOfficeId(savingsAccount.officeId()) // .withOfficeId(account.officeId()) //
.withClientId(savingsAccount.clientId()) // .withClientId(account.clientId()) //
.withGroupId(savingsAccount.groupId()) // .withGroupId(account.groupId()) //
.withSavingsId(savingsAccountId) // .withSavingsId(savingsAccountId) //
.build(); .build();
} }
@@ -733,8 +748,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(
savingsAccountChargeId, savingsAccountId); savingsAccountChargeId, savingsAccountId);
// TODO AA: validate before deleting a charge
savingsAccount.removeCharge(savingsAccountCharge); savingsAccount.removeCharge(savingsAccountCharge);
this.savingAccountRepository.saveAndFlush(savingsAccount); this.savingAccountRepository.saveAndFlush(savingsAccount);
@@ -747,31 +760,25 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.build(); .build();
} }
@Transactional
@Override @Override
public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) { public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) {
this.context.authenticatedUser(); this.context.authenticatedUser();
this.savingsAccountChargeDataValidator.validatePayCharge(command.json()); this.savingsAccountChargeDataValidator.validatePayCharge(command.json());
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(amountParamName);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount);
final BigDecimal chargeAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(
savingsAccountChargeId, savingsAccountId);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
//transaction date should not be on a holiday or non working day //transaction date should not be on a holiday or non working day
if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday()
&& this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), transactionDate)) { && this.holidayWritePlatformService.isHoliday(savingsAccountCharge.savingsAccount().officeId(), transactionDate)) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt)) baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt))
.failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday"); .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
@@ -784,49 +791,62 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, transactionDate, fmt, existingTransactionIds, existingReversedTransactionIds); this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt);
this.savingAccountRepository.saveAndFlush(savingsAccount);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withEntityId(savingsAccountChargeId) // .withEntityId(savingsAccountCharge.getId()) //
.withOfficeId(savingsAccount.officeId()) // .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
.withClientId(savingsAccount.clientId()) // .withClientId(savingsAccountCharge.savingsAccount().clientId()) //
.withGroupId(savingsAccount.groupId()) // .withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
.withSavingsId(savingsAccountId) // .withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
.build(); .build();
} }
@Transactional
@Override @Override
@CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES) @CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES)
public void payDueCharges() { public void processDueCharges() {
//always use current date as transaction date for batch job //always use current date as transaction date for batch job
final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
final List<SavingsAccountCharge> pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate()); final List<SavingsAccountCharge> pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate());
final List<SavingsAccountCharge> paidCharges = new ArrayList<SavingsAccountCharge>(pendingCharges.size()); //final List<SavingsAccountCharge> paidCharges = new ArrayList<SavingsAccountCharge>(pendingCharges.size());
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
for (SavingsAccountCharge savingsAccountCharge : pendingCharges) { for (SavingsAccountCharge savingsAccountCharge : pendingCharges) {
// Get Savings account from savings charge
final SavingsAccount account = savingsAccountCharge.savingsAccount();
this.savingAccountAssembler.assignSavingAccountHelpers(account);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
try { try {
account.payDueCharge(savingsAccountCharge, transactionDate, existingTransactionIds, existingReversedTransactionIds); payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt);
paidCharges.add(savingsAccountCharge);
} catch (InsufficientAccountBalanceException isab) { } catch (InsufficientAccountBalanceException isab) {
// TODO: AA - provide a meaningful error with failed // TODO: AA - provide a meaningful error with failed
// transactions list // transactions list
} }
} }
this.savingsAccountChargeRepository.save(paidCharges); // this.savingsAccountChargeRepository.save(paidCharges);
} }
@Transactional
private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid,
final DateTimeFormatter formatter) {
// Get Savings account from savings charge
final SavingsAccount account = savingsAccountCharge.savingsAccount();
this.savingAccountAssembler.assignSavingAccountHelpers(account);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, existingTransactionIds,
existingReversedTransactionIds);
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
account.validateAccountBalanceDoesNotBecomeNegative(amountPaid);
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
}
} }