From 7330ed08ea8c1905c4532996cd6d1f88d7cffabc Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Tue, 24 Sep 2013 18:15:12 +0530 Subject: [PATCH] Savings fee code cleanup and added shcedule for paying charges --- api-docs/apiLive.htm | 2 + ...batch_job_entry_for_pay_savings_charge.sql | 1 + .../infrastructure/jobs/service/JobName.java | 2 +- .../service/HolidayWritePlatformService.java | 5 + ...WritePlatformServiceJpaRepositoryImpl.java | 19 ++- .../WorkingDaysWritePlatformService.java | 10 ++ ...WritePlatformServiceJpaRepositoryImpl.java | 34 +++++ .../charge/api/ChargesApiResource.java | 3 +- .../portfolio/charge/data/ChargeData.java | 55 ++++++-- .../ChargeDropdownReadPlatformService.java | 9 ++ ...ChargeDropdownReadPlatformServiceImpl.java | 27 ++++ .../ChargeReadPlatformServiceImpl.java | 10 +- .../LoanChargeReadPlatformServiceImpl.java | 13 +- .../savings/SavingsApiConstants.java | 3 + .../api/SavingsAccountChargesApiResource.java | 2 +- .../SavingsAccountChargeDataValidator.java | 29 +++- .../savings/domain/SavingsAccount.java | 80 +++++++---- .../savings/domain/SavingsAccountCharge.java | 26 +++- .../SavingsAccountChargeRepository.java | 8 ++ ...SavingsAccountChargeRepositoryWrapper.java | 12 ++ .../savings/domain/SavingsAccountSummary.java | 10 ++ .../domain/SavingsAccountTransaction.java | 22 +++ ...vingsAccountTransactionSummaryWrapper.java | 24 +++- ...iveSavingsAccountChargeCommandHandler.java | 2 +- ...sAccountChargeReadPlatformServiceImpl.java | 11 +- .../SavingsAccountWritePlatformService.java | 4 +- ...WritePlatformServiceJpaRepositoryImpl.java | 132 +++++++++++++++--- 27 files changed, 482 insertions(+), 73 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index d84d39975..40b998a06 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -14344,8 +14344,10 @@ POST savingsaccounts/1/charges/2?command=paycharge Content-Type: application/json Request Body: { + "dateFormat": "dd MMMM yyyy", "locale": "en", "amount": "60", + "dueDate": "12 September 2013" } diff --git a/mifosng-db/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql b/mifosng-db/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql new file mode 100644 index 000000000..fd5fb5892 --- /dev/null +++ b/mifosng-db/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql @@ -0,0 +1 @@ +INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Pay Due Savings Charges', 'Pay Due Savings Charges', '0 0 12 * * ?', '2013-09-23 00:00:00', 5, NULL, NULL, NULL, 'Pay Due Savings ChargesJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java index 57fc53e28..022458f69 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java @@ -5,7 +5,7 @@ public enum JobName { UPDATE_LOAN_SUMMARY("Update loan Summary"), UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), UPDATE_LOAN_PAID_IN_ADVANCE( "Update Loan Paid In Advance"), APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS( "Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS("Post Interest For Savings"), TRANSFER_FEE_CHARGE_FOR_LOANS( - "Transfer Fee For Loans From Savings"); + "Transfer Fee For Loans From Savings"), PAY_DUE_SAVINGS_CHARGES("Pay Due Savings Charges"); private final String name; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java index 593cf6523..6f702fa43 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java @@ -1,9 +1,14 @@ package org.mifosplatform.organisation.holiday.service; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; public interface HolidayWritePlatformService { CommandProcessingResult createHoliday(JsonCommand command); + + boolean isHoliday(Long officeId, LocalDate transactionDate); + + boolean isTransactionAllowedOnHoliday(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java index 68c8a8b05..db8578c2d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java @@ -1,9 +1,11 @@ package org.mifosplatform.organisation.holiday.service; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -39,7 +41,7 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit private final HolidayDataValidator fromApiJsonDeserializer; private final HolidayRepository holidayRepository; private final WorkingDaysRepositoryWrapper daysRepositoryWrapper; - + private final ConfigurationDomainService configurationDomainService; private final PlatformSecurityContext context; private final OfficeRepository officeRepository; private final FromJsonHelper fromApiJsonHelper; @@ -47,13 +49,14 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit @Autowired public HolidayWritePlatformServiceJpaRepositoryImpl(final HolidayDataValidator fromApiJsonDeserializer, final HolidayRepository holidayRepository, final PlatformSecurityContext context, final OfficeRepository officeRepository, - final FromJsonHelper fromApiJsonHelper, final WorkingDaysRepositoryWrapper daysRepositoryWrapper) { + final FromJsonHelper fromApiJsonHelper, final WorkingDaysRepositoryWrapper daysRepositoryWrapper, final ConfigurationDomainService configurationDomainService) { this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.holidayRepository = holidayRepository; this.context = context; this.officeRepository = officeRepository; this.fromApiJsonHelper = fromApiJsonHelper; this.daysRepositoryWrapper = daysRepositoryWrapper; + this.configurationDomainService = configurationDomainService; } @Transactional @@ -152,4 +155,16 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit toDate.toString(), repaymentsRescheduledTo.toString()); } } + + @Override + public boolean isHoliday(Long officeId, LocalDate transactionDate) { + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, transactionDate.toDate()); + return HolidayUtil.isHoliday(transactionDate, holidays); + } + + @Override + public boolean isTransactionAllowedOnHoliday() { + return this.configurationDomainService.allowTransactionsOnHolidayEnabled(); + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformService.java new file mode 100644 index 000000000..e71ae2ffd --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformService.java @@ -0,0 +1,10 @@ +package org.mifosplatform.organisation.workingdays.service; + +import org.joda.time.LocalDate; + +public interface WorkingDaysWritePlatformService { + + boolean isWorkingDay(LocalDate transactionDate); + + boolean isTransactionAllowedOnNonWorkingDay(); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..4e2288715 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,34 @@ +package org.mifosplatform.organisation.workingdays.service; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; +import org.mifosplatform.organisation.workingdays.domain.WorkingDays; +import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class WorkingDaysWritePlatformServiceJpaRepositoryImpl implements WorkingDaysWritePlatformService { + + private final WorkingDaysRepositoryWrapper daysRepositoryWrapper; + private final ConfigurationDomainService configurationDomainService; + + @Autowired + public WorkingDaysWritePlatformServiceJpaRepositoryImpl(final WorkingDaysRepositoryWrapper daysRepositoryWrapper, + final ConfigurationDomainService configurationDomainService) { + this.daysRepositoryWrapper = daysRepositoryWrapper; + this.configurationDomainService = configurationDomainService; + } + + @Override + public boolean isWorkingDay(LocalDate transactionDate) { + final WorkingDays workingDays = this.daysRepositoryWrapper.findOne(); + return WorkingDaysUtil.isWorkingDay(workingDays, transactionDate); + } + + @Override + public boolean isTransactionAllowedOnNonWorkingDay() { + return this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiResource.java index b5d8e2261..154c7f108 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiResource.java @@ -43,7 +43,8 @@ public class ChargesApiResource { private final Set CHARGES_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "name", "amount", "currency", "penalty", "active", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", - "chargeAppliesToOptions", "chargeTimeTypeOptions", "currencyOptions")); + "chargeAppliesToOptions", "chargeTimeTypeOptions", "currencyOptions", "loanChargeCalculationTypeOptions", + "loanChargeTimeTypeOptions", "savingsChargeCalculationTypeOptions", "savingsChargeTimeTypeOptions")); private final String resourceNameForPermissions = "CHARGE"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java index 7ad450aa7..64252a5a3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java @@ -33,30 +33,53 @@ public class ChargeData implements Comparable, Serializable { private final EnumOptionData chargePaymentMode; private final Collection currencyOptions; - private final List chargeCalculationTypeOptions; - private final List chargeAppliesToOptions; - private final List chargeTimeTypeOptions; - private final List chargePaymetModeOptions; + private final List chargeCalculationTypeOptions;// + private final List chargeAppliesToOptions;// + private final List chargeTimeTypeOptions;// + private final List chargePaymetModeOptions;// + + private final List loanChargeCalculationTypeOptions; + private final List loanChargeTimeTypeOptions; + private final List savingsChargeCalculationTypeOptions; + private final List savingsChargeTimeTypeOptions; public static ChargeData template(final Collection currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, - final List chargeTimeTypeOptions, final List chargePaymentModeOptions) { + final List chargeTimeTypeOptions, final List chargePaymentModeOptions, + final List loansChargeCalculationTypeOptions, final List loansChargeTimeTypeOptions, + final List savingsChargeCalculationTypeOptions, final List savingsChargeTimeTypeOptions) { + return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions, - chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions); + chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, + loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); } public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) { return new ChargeData(charge.id, charge.name, charge.amount, charge.currency, charge.chargeTimeType, charge.chargeAppliesTo, charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, template.currencyOptions, template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions, - template.chargePaymetModeOptions); + template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions, + template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions); } public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType, final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active) { + + final Collection currencyOptions = null; + final List chargeCalculationTypeOptions = null; + final List chargeAppliesToOptions = null; + final List chargeTimeTypeOptions = null; + final List chargePaymentModeOptions = null; + final List loansChargeCalculationTypeOptions = null; + final List loansChargeTimeTypeOptions = null; + final List savingsChargeCalculationTypeOptions = null; + final List savingsChargeTimeTypeOptions = null; + return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, - penalty, active, null, null, null, null, null); + penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, + chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, + savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); } public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) { @@ -73,9 +96,15 @@ public class ChargeData implements Comparable, Serializable { final List chargeAppliesToOptions = null; final List chargeTimeTypeOptions = null; final List chargePaymentModeOptions = null; + final List loansChargeCalculationTypeOptions = null; + final List loansChargeTimeTypeOptions = null; + final List savingsChargeCalculationTypeOptions = null; + final List savingsChargeTimeTypeOptions = null; + return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, - chargePaymentModeOptions); + chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, + savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); } private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, @@ -83,7 +112,9 @@ public class ChargeData implements Comparable, Serializable { final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active, final Collection currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, final List chargeTimeTypeOptions, - final List chargePaymentModeOptions) { + final List chargePaymentModeOptions, final List loansChargeCalculationTypeOptions, + final List loansChargeTimeTypeOptions, final List savingsChargeCalculationTypeOptions, + final List savingsChargeTimeTypeOptions) { this.id = id; this.name = name; this.amount = amount; @@ -99,6 +130,10 @@ public class ChargeData implements Comparable, Serializable { this.chargeAppliesToOptions = chargeAppliesToOptions; this.chargeTimeTypeOptions = chargeTimeTypeOptions; this.chargePaymetModeOptions = chargePaymentModeOptions; + this.savingsChargeCalculationTypeOptions = savingsChargeCalculationTypeOptions; + this.savingsChargeTimeTypeOptions = savingsChargeTimeTypeOptions; + this.loanChargeCalculationTypeOptions = loansChargeCalculationTypeOptions; + this.loanChargeTimeTypeOptions = loansChargeTimeTypeOptions; } @Override diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java index f5e445e47..60f6c9e17 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java @@ -18,4 +18,13 @@ public interface ChargeDropdownReadPlatformService { List retrieveCollectionTimeTypes(); List retrivePaymentModes(); + + List retrieveLoanCalculationTypes(); + + List retrieveLoanCollectionTimeTypes(); + + List retrieveSavingsCalculationTypes(); + + List retrieveSavingsCollectionTimeTypes(); + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java index fcf2d9998..0114eb0eb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.charge.service; import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeCalculationType; import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargePaymentMode; +import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeTimeType; import java.util.ArrayList; import java.util.Arrays; @@ -68,4 +69,30 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead public List retrivePaymentModes() { return Arrays.asList(chargePaymentMode(ChargePaymentMode.REGULAR), chargePaymentMode(ChargePaymentMode.ACCOUNT_TRANSFER)); } + + @Override + public List retrieveLoanCalculationTypes() { + return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT)); + } + + @Override + public List retrieveLoanCollectionTimeTypes() { + return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE)); + } + + @Override + public List retrieveSavingsCalculationTypes() { + return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT) + //Enable when percentage of amount is supported for savings charges + //, chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT) + ); + } + + @Override + public List retrieveSavingsCollectionTimeTypes() { + return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE) + //enable when support is added for recurring Monthly and yearly charges + //, chargeTimeType(ChargeTimeType.MONTHLY), chargeTimeType(ChargeTimeType.YEARLY) + ); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java index 424852e07..15926d63d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java @@ -82,9 +82,17 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final List allowedChargeAppliesToOptions = this.chargeDropdownReadPlatformService.retrieveApplicableToTypes(); final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); final List chargePaymentOptions = this.chargeDropdownReadPlatformService.retrivePaymentModes(); + final List loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveLoanCalculationTypes(); + final List loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes(); + final List savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCalculationTypes(); + final List savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCollectionTimeTypes(); return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions, - allowedChargeTimeOptions, chargePaymentOptions); + allowedChargeTimeOptions, chargePaymentOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, + savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java index 939b1b787..d4bcac095 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java @@ -115,8 +115,17 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final List allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes(); final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); - - return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null); + final List loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveLoanCalculationTypes(); + final List loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes(); + final List savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCalculationTypes(); + final List savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCollectionTimeTypes(); + + return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null, + loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, + savingsChargeTimeTypeOptions); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index fd9cab5d9..5a77b82ea 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -192,4 +192,7 @@ public class SavingsApiConstants { public static final Set SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); + + public static final Set SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( + amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java index 97c3f343c..458cb4ed1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java @@ -155,7 +155,7 @@ public class SavingsAccountChargesApiResource { @Path("{savingsAccountChargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String waiveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId, + public String payOrWaiveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId, @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java index 7949ad3a2..50161bfed 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java @@ -6,6 +6,7 @@ package org.mifosplatform.portfolio.savings.data; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; @@ -18,6 +19,7 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; @@ -57,7 +59,8 @@ public class SavingsAccountChargeDataValidator { baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); if (this.fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) { - this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull(); } throwExceptionIfValidationWarningsExist(dataValidationErrors); @@ -79,12 +82,34 @@ public class SavingsAccountChargeDataValidator { baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); if (this.fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) { - this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull(); } throwExceptionIfValidationWarningsExist(dataValidationErrors); } + public void validatePayCharge(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element); + baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); + + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } 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 6cd2e34db..41987277b 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 @@ -14,6 +14,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeri import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import java.math.BigDecimal; import java.math.MathContext; @@ -735,7 +736,7 @@ public class SavingsAccount extends AbstractPersistable { return transactionBeforeLastInterestPosting; } - + public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount) { final List transactionsSortedByDate = retreiveListOfTransactions(); Money runningBalance = Money.zero(this.currency); @@ -1893,7 +1894,6 @@ public class SavingsAccount extends AbstractPersistable { } public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) { - // TODO AA: revisit if (this.currency == null) { return false; } return this.currency.getCode().equalsIgnoreCase(matchingCurrencyCode); } @@ -1907,7 +1907,7 @@ public class SavingsAccount extends AbstractPersistable { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); if (isClosed()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed"); @@ -1935,13 +1935,6 @@ public class SavingsAccount extends AbstractPersistable { // waive charge final Money amountWaived = savingsAccountCharge.waive(getCurrency()); - // TODO : AA revisit the code - /* - * Money feeChargesWaived = amountWaived; Money penaltyChargesWaived = - * Money.zero(this.getCurrency()); if - * (savingsAccountCharge.isPenaltyCharge()) { penaltyChargesWaived = - * amountWaived; feeChargesWaived = Money.zero(this.getCurrency()); } - */ // persist an entry in transaction details final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), @@ -1958,18 +1951,13 @@ public class SavingsAccount extends AbstractPersistable { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); if (isClosed()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (isNotActive()) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - if (!hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "transaction.invalid.account.currency.and.charge.currency.not.same"); @@ -1979,7 +1967,7 @@ public class SavingsAccount extends AbstractPersistable { final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate(); if (chargeDueDate != null && chargeDueDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter("dueDate").value(getActivationLocalDate().toString(formatter)) + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -1991,12 +1979,12 @@ public class SavingsAccount extends AbstractPersistable { } - public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final List existingTransactionIds, - final List existingReversedTransactionIds) { + public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final LocalDate transactionDate, + final DateTimeFormatter formatter, final List existingTransactionIds, final List existingReversedTransactionIds) { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); if (isClosed()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed"); @@ -2008,6 +1996,12 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + if (transactionDate != null && transactionDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId); // validate charge is not already paid or waived @@ -2018,17 +2012,19 @@ public class SavingsAccount extends AbstractPersistable { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + + final Money amountPaidMoney = Money.of(currency, amountPaid); + if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(amountPaidMoney)){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - final Money chargePaid = savingsAccountCharge.pay(getCurrency(), amountPaid); - - // If null reset to current date - final LocalDate transactionDate = (savingsAccountCharge.getDueLocalDate() == null) ? DateUtils.getLocalDateOfTenant() - : savingsAccountCharge.getDueLocalDate(); - - // persist an entry in transaction details + final Money chargePaid = savingsAccountCharge.pay(getCurrency(), amountPaidMoney); + + // persist transaction details final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, chargePaid); final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, @@ -2051,6 +2047,36 @@ public class SavingsAccount extends AbstractPersistable { } + public void payDueCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + final Money amountPaid = savingsAccountCharge.payDueCharge(getCurrency()); + + // persist transaction details + final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, amountPaid); + + final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, + amountPaid.getAmount()); + + 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(amountPaid.getAmount()); + } + private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) { SavingsAccountCharge charge = null; for (final SavingsAccountCharge existingCharge : this.charges) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java index 33c5efa66..4b296b9f6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java @@ -228,13 +228,25 @@ public class SavingsAccountCharge extends AbstractPersistable { return getAmountWaived(currency); } - public Money pay(final MonetaryCurrency currency, final BigDecimal chargePaid) { + public Money pay(final MonetaryCurrency currency, final Money amountPaid) { Money amountPaidToDate = Money.of(currency, this.amountPaid); - final Money amountPaid = Money.of(currency, chargePaid); Money amountOutstanding = Money.of(currency, this.amountOutstanding); amountPaidToDate = amountPaidToDate.plus(amountPaid); - amountOutstanding = amountOutstanding.minus(chargePaid); + amountOutstanding = amountOutstanding.minus(amountPaid); + this.amountPaid = amountPaidToDate.getAmount(); + this.amountOutstanding = amountOutstanding.getAmount(); + this.paid = determineIfFullyPaid(); + return amountPaid; + } + + 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(); @@ -440,6 +452,10 @@ public class SavingsAccountCharge extends AbstractPersistable { return Money.of(currency, this.amountWrittenOff); } + public Money getAmountOutstanding(final MonetaryCurrency currency){ + return Money.of(currency, this.amountOutstanding); + } + /** * @param incrementBy * Amount used to pay off this charge @@ -486,6 +502,10 @@ public class SavingsAccountCharge extends AbstractPersistable { return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate(); } + public SavingsAccount savingsAccount(){ + return this.savingsAccount; + } + @Override public boolean equals(final Object obj) { if (obj == null) { return false; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java index 38e007ffa..959e22f8f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java @@ -5,11 +5,19 @@ */ package org.mifosplatform.portfolio.savings.domain; +import java.util.Date; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface SavingsAccountChargeRepository extends JpaRepository, JpaSpecificationExecutor { SavingsAccountCharge findByIdAndSavingsAccountId(Long id, Long savingsAccountId); + + @Query("from SavingsAccountCharge sac where sac.dueDate <=:transactionDate and sac.waived = 0 and sac.paid=0") + List findPendingCharges(@Param("transactionDate") Date transactionDate); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java index 87bebc69e..68187bbd6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java @@ -5,6 +5,10 @@ */ package org.mifosplatform.portfolio.savings.domain; +import java.util.Collection; +import java.util.Date; +import java.util.List; + import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -36,11 +40,19 @@ public class SavingsAccountChargeRepositoryWrapper { if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(id); } return savingsAccountCharge; } + + public List findPendingCharges(final Date transactionDate){ + return this.repository.findPendingCharges(transactionDate); + } public void save(final SavingsAccountCharge savingsAccountCharge) { this.repository.save(savingsAccountCharge); } + public void save(final Iterable savingsAccountCharges){ + this.repository.save(savingsAccountCharges); + } + public void saveAndFlush(final SavingsAccountCharge savingsAccountCharge) { this.repository.saveAndFlush(savingsAccountCharge); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java index 91e26bcdd..3a79cef14 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java @@ -10,6 +10,7 @@ import java.util.List; import javax.persistence.Column; import javax.persistence.Embeddable; +import javax.persistence.Transient; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; @@ -49,6 +50,13 @@ public final class SavingsAccountSummary { @Column(name = "account_balance_derived", scale = 6, precision = 19) private BigDecimal accountBalance = BigDecimal.ZERO; + //TODO: AA do we need this data to be persisted. + @Transient + private BigDecimal totalFeeChargesWaived = BigDecimal.ZERO; + + @Transient + private BigDecimal totalPenaltyChargesWaived = BigDecimal.ZERO; + protected SavingsAccountSummary() { // } @@ -63,6 +71,8 @@ public final class SavingsAccountSummary { this.totalAnnualFees = wrapper.calculateTotalAnnualFees(currency, transactions); this.totalFeeCharge = wrapper.calculateTotalFeesCharge(currency, transactions); this.totalPenaltyCharge = wrapper.calculateTotalPenaltyCharge(currency, transactions); + this.totalFeeChargesWaived = wrapper.calculateTotalFeesChargeWaived(currency, transactions); + this.totalPenaltyChargesWaived = wrapper.calculateTotalPenaltyChargeWaived(currency, transactions); this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 7475e64aa..29005cbb5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -437,6 +437,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable { public boolean isCharge() { return SavingsAccountTransactionType.fromInt(this.typeOf).isCharge(); } + + public boolean isWaiveCharge(){ + return SavingsAccountTransactionType.fromInt(this.typeOf).isWaiveCharge(); + } public boolean isFeeCharge() { final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); @@ -456,6 +460,24 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return isPenaltyCharge() && isNotReversed(); } + public boolean isWaiveFeeCharge() { + final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); + return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false; + } + + public boolean isWaivePenaltyCharge() { + final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); + return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false; + } + + public boolean isWaiveFeeChargeAndNotReversed() { + return isWaiveFeeCharge() && isNotReversed(); + } + + public boolean isWaivePenaltyChargeAndNotReversed() { + return isWaivePenaltyCharge() && isNotReversed(); + } + private SavingsAccountChargePaidBy getSavingsAccountChargePaidBy() { if (!CollectionUtils.isEmpty(this.savingsAccountChargesPaid)) { return this.savingsAccountChargesPaid.iterator().next(); } return null; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java index b75caf58a..c61ed8fc1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java @@ -72,17 +72,37 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalFeesCharge(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isFeeChargeAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isFeeChargeAndNotReversed()) { total = total.plus(transaction.getAmount(currency)); } } return total.getAmountDefaultedToNullIfZero(); } + public BigDecimal calculateTotalFeesChargeWaived(final MonetaryCurrency currency, final List transactions) { + Money total = Money.zero(currency); + for (final SavingsAccountTransaction transaction : transactions) { + if (transaction.isWaiveFeeChargeAndNotReversed()) { + total = total.plus(transaction.getAmount(currency)); + } + } + return total.getAmountDefaultedToNullIfZero(); + } + public BigDecimal calculateTotalPenaltyCharge(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isPenaltyChargeAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isPenaltyChargeAndNotReversed()) { + total = total.plus(transaction.getAmount(currency)); + } + } + return total.getAmountDefaultedToNullIfZero(); + } + + public BigDecimal calculateTotalPenaltyChargeWaived(final MonetaryCurrency currency, final List transactions) { + Money total = Money.zero(currency); + for (final SavingsAccountTransaction transaction : transactions) { + if (transaction.isWaivePenaltyChargeAndNotReversed()) { total = total.plus(transaction.getAmount(currency)); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java index e9678a379..8630ea097 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java @@ -27,6 +27,6 @@ public class WaiveSavingsAccountChargeCommandHandler implements NewCommandSource @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.waiveCharge(command.getSavingsId(), command.entityId(), command); + return this.writePlatformService.waiveCharge(command.getSavingsId(), command.entityId()); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java index 656ad16b1..adf756355 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java @@ -112,9 +112,18 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou final List allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes(); final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); + final List loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveLoanCalculationTypes(); + final List loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes(); + final List savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCalculationTypes(); + final List savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCollectionTimeTypes(); // TODO AA : revisit for merge conflict - Not sure method signature - return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null); + return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null, + loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, + savingsChargeTimeTypeOptions); } @Override 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 d9f75c0ee..77130b111 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 @@ -48,7 +48,9 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult deleteSavingsAccountCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); - CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); + CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId); CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); + + void payDueCharges(); } \ No newline at end of file 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 56eeefc9b..63fb352f3 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 @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.savings.service; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import java.math.BigDecimal; import java.math.MathContext; @@ -34,11 +35,13 @@ import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.jobs.annotation.CronTarget; import org.mifosplatform.infrastructure.jobs.service.JobName; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.holiday.service.HolidayWritePlatformService; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.staff.domain.Staff; +import org.mifosplatform.organisation.workingdays.service.WorkingDaysWritePlatformService; import org.mifosplatform.portfolio.account.PortfolioAccountType; import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService; import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService; @@ -65,6 +68,7 @@ import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository; import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionRepository; +import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; import org.mifosplatform.portfolio.savings.exception.SavingsAccountClosingNotAllowedException; import org.mifosplatform.portfolio.savings.exception.SavingsAccountTransactionNotFoundException; import org.mifosplatform.portfolio.savings.exception.TransactionUpdateNotAllowedException; @@ -91,6 +95,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; private final ChargeRepositoryWrapper chargeRepository; private final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository; + private final HolidayWritePlatformService holidayWritePlatformService; + private final WorkingDaysWritePlatformService workingDaysWritePlatformService; @Autowired public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -105,7 +111,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountDomainService savingsAccountDomainService, final NoteRepository noteRepository, final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, - final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository) { + final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository, + final HolidayWritePlatformService holidayWritePlatformService, + final WorkingDaysWritePlatformService workingDaysWritePlatformService) { this.context = context; this.savingAccountRepository = savingAccountRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; @@ -121,6 +129,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService; this.chargeRepository = chargeRepository; this.savingsAccountChargeRepository = savingsAccountChargeRepository; + this.holidayWritePlatformService = holidayWritePlatformService; + this.workingDaysWritePlatformService = workingDaysWritePlatformService; } @Transactional @@ -586,6 +596,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command) { this.context.authenticatedUser(); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + final Long savingsAccountId = command.getSavingsId(); this.savingsAccountChargeDataValidator.validateAdd(command.json()); @@ -599,6 +613,23 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command); + if (savingsAccountCharge.getDueLocalDate() != null) { + // transaction date should not be on a holiday or non working day + if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() + && this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() + && !this.workingDaysWritePlatformService.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + } + savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition); this.savingAccountRepository.saveAndFlush(savingsAccount); @@ -616,9 +647,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand command) { + this.context.authenticatedUser(); - this.savingsAccountChargeDataValidator.validateUpdate(command.json()); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + final Long savingsAccountId = command.getSavingsId(); // SavingsAccount Charge entity final Long savingsChargeId = command.entityId(); @@ -630,20 +664,30 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi savingsAccountId); final Map changes = savingsAccountCharge.update(command); + + if (savingsAccountCharge.getDueLocalDate() != null) { + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + // transaction date should not be on a holiday or non working day + if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() + && this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() + && !this.workingDaysWritePlatformService.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + } + this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge); - // TODO AA: revisit the code - /* - * // Charges may be edited only when the loan associated with them are - * // yet to be approved (are in submitted and pending status) if - * (!loan.status().isSubmittedAndPendingApproval()) { throw new - * LoanChargeCannotBeUpdatedException( - * LOAN_CHARGE_CANNOT_BE_UPDATED_REASON - * .LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, - * loanCharge.getId()); } - */ return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // .withEntityId(savingsChargeId) // // .withOfficeId(savings) // // .withClientId(loan.getClientId()) // @@ -655,7 +699,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override - public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) { + public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { this.context.authenticatedUser(); @@ -670,7 +714,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepository.saveAndFlush(savingsAccount); return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // .withEntityId(savingsAccountChargeId) // .withOfficeId(savingsAccount.officeId()) // .withClientId(savingsAccount.clientId()) // @@ -707,21 +750,45 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) { - this.context.authenticatedUser(); + this.context.authenticatedUser(); + this.savingsAccountChargeDataValidator.validatePayCharge(command.json()); + final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); checkClientOrGroupActive(savingsAccount); final BigDecimal chargeAmount = command.bigDecimalValueOfParameterNamed(amountParamName); - savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, existingTransactionIds, existingReversedTransactionIds); + final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + //transaction date should not be on a holiday or non working day + if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() + && this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), transactionDate)) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() + && !this.workingDaysWritePlatformService.isWorkingDay(transactionDate)) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.a.nonworking.day"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, transactionDate, fmt, existingTransactionIds, existingReversedTransactionIds); this.savingAccountRepository.saveAndFlush(savingsAccount); return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // .withEntityId(savingsAccountChargeId) // .withOfficeId(savingsAccount.officeId()) // .withClientId(savingsAccount.clientId()) // @@ -731,4 +798,33 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } + @Transactional + @Override + @CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES) + public void payDueCharges() { + //always use current date as transaction date for batch job + final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); + final List pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate()); + final List paidCharges = new ArrayList(pendingCharges.size()); + for (SavingsAccountCharge savingsAccountCharge : pendingCharges) { + // Get Savings account from savings charge + final SavingsAccount account = savingsAccountCharge.savingsAccount(); + this.savingAccountAssembler.assignSavingAccountHelpers(account); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + try { + account.payDueCharge(savingsAccountCharge, transactionDate, existingTransactionIds, existingReversedTransactionIds); + paidCharges.add(savingsAccountCharge); + } catch (InsufficientAccountBalanceException isab) { + // TODO: AA - provide a meaningful error with failed + // transactions list + } + } + + this.savingsAccountChargeRepository.save(paidCharges); + } + + + } \ No newline at end of file