MIFOSX-397: support apply of annual fees on savings account through manual and scheduled job approach.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
INSERT INTO `m_permission`
|
||||
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
|
||||
VALUES
|
||||
('transaction_savings', 'APPLYANNUALFEE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPLYANNUALFEE', 1),
|
||||
('transaction_savings', 'APPLYANNUALFEE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPLYANNUALFEE', 0);
|
||||
@@ -466,6 +466,10 @@ public class CommandWrapper {
|
||||
return this.actionName.equalsIgnoreCase("POSTINTEREST") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
|
||||
}
|
||||
|
||||
public boolean isSavingsAccountApplyAnnualFee() {
|
||||
return this.actionName.equalsIgnoreCase("APPLYANNUALFEE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
|
||||
}
|
||||
|
||||
public boolean isSavingsAccountUndoTransaction() {
|
||||
return this.actionName.equalsIgnoreCase("UNDOTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
|
||||
}
|
||||
|
||||
+14
-1
@@ -47,6 +47,11 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder withNoJsonBody() {
|
||||
this.json = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateGlobalConfiguration() {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "CONFIGURATION";
|
||||
@@ -850,6 +855,15 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder savingsAccountApplyAnnualFees(final Long accountId) {
|
||||
this.actionName = "APPLYANNUALFEE";
|
||||
this.entityName = "SAVINGSACCOUNT";
|
||||
this.savingsId = accountId;
|
||||
this.entityId = accountId;
|
||||
this.href = "/savingsaccounts/" + accountId + "?command=applyAnnualFees";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createCalendar(final String supportedEntityType, final Long supportedEntityId) {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "CALENDAR";
|
||||
@@ -1168,5 +1182,4 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/updateJobDetail/" + jobId+"/updateJobDetail";
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -401,6 +401,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
handler = this.applicationContext.getBean("calculateInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isSavingsAccountInterestPosting()) {
|
||||
handler = this.applicationContext.getBean("postInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isSavingsAccountApplyAnnualFee()) {
|
||||
handler = this.applicationContext.getBean("applyAnnualFeeSavingsAccountCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isSavingsAccountUndoTransaction()) {
|
||||
handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class);
|
||||
} else {
|
||||
|
||||
+7
-1
@@ -97,7 +97,13 @@ public class FromJsonHelper {
|
||||
}
|
||||
|
||||
public JsonElement parse(final String json) {
|
||||
return parser.parse(json);
|
||||
|
||||
JsonElement parsedElement = null;
|
||||
if (StringUtils.isNotBlank(json)) {
|
||||
parsedElement = parser.parse(json);
|
||||
}
|
||||
|
||||
return parsedElement;
|
||||
}
|
||||
|
||||
public boolean parameterExists(final String parameterName, final JsonElement element) {
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ public class SavingsApiConstants {
|
||||
public static String modifyApplicationAction = ".modify";
|
||||
public static String deleteApplicationAction = ".delete";
|
||||
public static String undoTransactionAction = ".undotransaction";
|
||||
public static String applyAnnualFeeTransactionAction = ".applyannualfee";
|
||||
|
||||
// general
|
||||
public static final String localeParamName = "locale";
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ public class SavingsAccountTransactionsApiResource {
|
||||
@Path("{transactionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String transaction(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId,
|
||||
public String undoTransaction(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId,
|
||||
@QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
|
||||
|
||||
String jsonApiRequest = apiRequestBodyAsJson;
|
||||
|
||||
+4
-2
@@ -164,7 +164,6 @@ public class SavingsAccountsApiResource {
|
||||
SavingsAccountData templateData = null;
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
if (settings.isTemplate()) {
|
||||
|
||||
templateData = this.savingsAccountReadPlatformService.retrieveTemplate(savingsAccount.clientId(), savingsAccount.groupId(),
|
||||
savingsAccount.productId(), staffInSelectedOfficeOnly);
|
||||
}
|
||||
@@ -217,11 +216,14 @@ public class SavingsAccountsApiResource {
|
||||
final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
} else if (is(commandParam, "calculateInterest")) {
|
||||
final CommandWrapper commandRequest = builder.withJson(null).savingsAccountInterestCalculation(accountId).build();
|
||||
final CommandWrapper commandRequest = builder.withNoJsonBody().savingsAccountInterestCalculation(accountId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
} else if (is(commandParam, "postInterest")) {
|
||||
final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(accountId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
} else if (is(commandParam, "applyAnnualFees")) {
|
||||
final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(accountId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.savings.data;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
public class SavingsAccountAnnualFeeData {
|
||||
|
||||
private final Long id;
|
||||
private final String accountNo;
|
||||
private final LocalDate nextAnnualFeeDueDate;
|
||||
|
||||
public static SavingsAccountAnnualFeeData instance(final Long id, final String accountNo, final LocalDate nextAnnualFeeDueDate) {
|
||||
return new SavingsAccountAnnualFeeData(id, accountNo, nextAnnualFeeDueDate);
|
||||
}
|
||||
|
||||
private SavingsAccountAnnualFeeData(final Long id, final String accountNo, final LocalDate nextAnnualFeeDueDate) {
|
||||
this.id = id;
|
||||
this.accountNo = accountNo;
|
||||
this.nextAnnualFeeDueDate = nextAnnualFeeDueDate;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getAccountNo() {
|
||||
return this.accountNo;
|
||||
}
|
||||
|
||||
public LocalDate getNextAnnualFeeDueDate() {
|
||||
return this.nextAnnualFeeDueDate;
|
||||
}
|
||||
}
|
||||
+4
@@ -248,6 +248,10 @@ public class SavingsAccountData {
|
||||
this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions;
|
||||
}
|
||||
|
||||
public Long id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public Long clientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
+2
-2
@@ -92,7 +92,7 @@ public class SavingsAccountDataValidator {
|
||||
|
||||
if (fromApiJsonHelper.parameterExists(fieldOfficerIdParamName, element)) {
|
||||
final Long fieldOfficerId = fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fieldOfficerIdParamName).value(fieldOfficerId).notNull().integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter(fieldOfficerIdParamName).value(fieldOfficerId).ignoreIfNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element);
|
||||
@@ -276,7 +276,7 @@ public class SavingsAccountDataValidator {
|
||||
|
||||
if (fromApiJsonHelper.parameterExists(fieldOfficerIdParamName, element)) {
|
||||
final Long fieldOfficerId = fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fieldOfficerIdParamName).value(fieldOfficerId).notNull().integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter(fieldOfficerIdParamName).value(fieldOfficerId).ignoreIfNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
if (fromApiJsonHelper.parameterExists(submittedOnDateParamName, element)) {
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.savings.data;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Immutable data object representing date
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class TransactionDateData {
|
||||
|
||||
private final LocalDate date;
|
||||
|
||||
public TransactionDateData(final LocalDate date) {
|
||||
this.date = date;
|
||||
}
|
||||
}
|
||||
+135
-71
@@ -230,7 +230,6 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
@Column(name = "annual_fee_on_day", nullable = true)
|
||||
private Integer annualFeeOnDay;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Temporal(TemporalType.DATE)
|
||||
@Column(name = "annual_fee_next_due_date", nullable = true)
|
||||
private Date annualFeeNextDueDate;
|
||||
@@ -311,22 +310,11 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
if (annualFeeOnMonthDay != null) {
|
||||
this.annualFeeOnMonth = annualFeeOnMonthDay.getMonthOfYear();
|
||||
this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth();
|
||||
|
||||
updateAnnualFeeNextDueDate();
|
||||
}
|
||||
|
||||
this.summary = new SavingsAccountSummary();
|
||||
}
|
||||
|
||||
private void updateAnnualFeeNextDueDate() {
|
||||
LocalDate nextDueLocalDate = new LocalDate().withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay);
|
||||
if (nextDueLocalDate.isBefore(DateUtils.getLocalDateOfTenant())) {
|
||||
nextDueLocalDate = nextDueLocalDate.plusYears(1);
|
||||
}
|
||||
|
||||
this.annualFeeNextDueDate = nextDueLocalDate.toDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used after fetching/hydrating a {@link SavingsAccount} object to inject
|
||||
* helper services/components used for update summary details after
|
||||
@@ -412,7 +400,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
SavingsAccountTransaction postingTransation = null;
|
||||
|
||||
for (SavingsAccountTransaction transaction : this.transactions) {
|
||||
if (transaction.isInterestPosting() && transaction.occursOn(postingDate)) {
|
||||
if (transaction.isInterestPostingAndNotReversed() && transaction.occursOn(postingDate)) {
|
||||
postingTransation = transaction;
|
||||
break;
|
||||
}
|
||||
@@ -492,7 +480,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
List<SavingsAccountTransaction> orderedNonInterestPostingTransactions = new ArrayList<SavingsAccountTransaction>();
|
||||
|
||||
for (SavingsAccountTransaction transaction : listOfTransactionsSorted) {
|
||||
if (!transaction.isInterestPosting() && transaction.isNotReversed()) {
|
||||
if (!transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) {
|
||||
orderedNonInterestPostingTransactions.add(transaction);
|
||||
}
|
||||
}
|
||||
@@ -523,12 +511,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
transaction.zeroBalanceFields();
|
||||
} else {
|
||||
Money transactionAmount = Money.zero(this.currency);
|
||||
if (transaction.isDeposit()) {
|
||||
if (transaction.isCredit()) {
|
||||
transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency));
|
||||
} else if (transaction.isWithdrawal()) {
|
||||
} else if (transaction.isDebit()) {
|
||||
transactionAmount = transactionAmount.minus(transaction.getAmount(this.currency));
|
||||
} else if (transaction.isInterestPosting()) {
|
||||
transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency));
|
||||
}
|
||||
|
||||
runningBalance = runningBalance.plus(transactionAmount);
|
||||
@@ -705,7 +691,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
boolean transactionBeforeLastInterestPosting = false;
|
||||
|
||||
for (SavingsAccountTransaction transaction : retreiveListOfTransactions()) {
|
||||
if (transaction.isNotReversed() && transaction.isInterestPosting() && transaction.isAfter(transactionDate)) {
|
||||
if (transaction.isNotReversed() && transaction.isInterestPostingAndNotReversed() && transaction.isAfter(transactionDate)) {
|
||||
transactionBeforeLastInterestPosting = true;
|
||||
break;
|
||||
}
|
||||
@@ -720,7 +706,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
Money runningBalance = Money.zero(this.currency);
|
||||
|
||||
for (SavingsAccountTransaction transaction : transactionsSortedByDate) {
|
||||
if (transaction.isNotReversed() && transaction.isDeposit() || transaction.isInterestPosting()) {
|
||||
if (transaction.isNotReversed() && transaction.isDeposit() || transaction.isInterestPostingAndNotReversed()) {
|
||||
runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
|
||||
} else if (transaction.isNotReversed() && transaction.isWithdrawal()) {
|
||||
runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
|
||||
@@ -740,7 +726,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
Money runningBalance = Money.zero(this.currency);
|
||||
|
||||
for (SavingsAccountTransaction transaction : transactionsSortedByDate) {
|
||||
if (transaction.isNotReversed() && transaction.isDeposit() || transaction.isInterestPosting()) {
|
||||
if (transaction.isNotReversed() && transaction.isDeposit() || transaction.isInterestPostingAndNotReversed()) {
|
||||
runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
|
||||
} else if (transaction.isNotReversed() && transaction.isWithdrawal()) {
|
||||
runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
|
||||
@@ -759,54 +745,51 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
public SavingsAccountTransaction addAnnualFee(final MathContext mc, final DateTimeFormatter formatter, final LocalDate transactionDate,
|
||||
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
|
||||
public SavingsAccountTransaction addAnnualFee(final MathContext mc, final DateTimeFormatter formatter,
|
||||
final LocalDate annualFeeTransactionDate, final LocalDate today, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds) {
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.applyAnnualFeeTransactionAction);
|
||||
|
||||
if (isNotActive()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active");
|
||||
|
||||
final String defaultUserMessage = "Transaction is not allowed. Account is not active.";
|
||||
final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.account.is.not.active",
|
||||
defaultUserMessage, "transactionDate", transactionDate.toString(formatter));
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||
}
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
dataValidationErrors.add(error);
|
||||
LocalDate nextAnnualFeeDueDate = getNextAnnualFeeDueDate();
|
||||
if (nextAnnualFeeDueDate == null || annualFeeSettingsNotSet()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.annualfee.settings");
|
||||
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
|
||||
if (isDateInTheFuture(transactionDate)) {
|
||||
final String defaultUserMessage = "Transaction date cannot be in the future.";
|
||||
final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.in.the.future",
|
||||
defaultUserMessage, "transactionDate", transactionDate.toString(formatter));
|
||||
if (nextAnnualFeeDueDate.isBefore(getActivationLocalDate())) {
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
dataValidationErrors.add(error);
|
||||
baseDataValidator.reset().parameter("annualFeeTransactionDate").value(getActivationLocalDate().toString(formatter))
|
||||
.failWithCodeNoParameterAddedToErrorCode("before.activationDate");
|
||||
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
|
||||
if (transactionDate.isBefore(getActivationLocalDate())) {
|
||||
final Object[] defaultUserArgs = Arrays.asList(transactionDate.toString(formatter),
|
||||
getActivationLocalDate().toString(formatter)).toArray();
|
||||
final String defaultUserMessage = "Transaction date cannot be before accounts activation date.";
|
||||
final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.before.activation.date",
|
||||
defaultUserMessage, "transactionDate", defaultUserArgs);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
dataValidationErrors.add(error);
|
||||
if (isDateInTheFuture(annualFeeTransactionDate)) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.in.the.future");
|
||||
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
|
||||
if (isAccountLocked(transactionDate)) {
|
||||
final String defaultUserMessage = "Withdrawal is not allowed. No withdrawals are allowed until after "
|
||||
+ getLockedInUntilLocalDate().toString(formatter);
|
||||
final ApiParameterError error = ApiParameterError.parameterError(
|
||||
"error.msg.savingsaccount.transaction.withdrawals.blocked.during.lockin.period", defaultUserMessage, "transactionDate",
|
||||
transactionDate.toString(formatter), getLockedInUntilLocalDate().toString(formatter));
|
||||
if (isNotValidAnnualFeeTransactionDate(annualFeeTransactionDate, today)) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("invalid.date");
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
dataValidationErrors.add(error);
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
|
||||
Date currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate();
|
||||
if (currentAnnualFeeNextDueDate != null && new LocalDate(currentAnnualFeeNextDueDate).isEqual(annualFeeTransactionDate)) {
|
||||
baseDataValidator.reset().parameter("annualFeeTransactionDate").value(annualFeeTransactionDate.toString(formatter))
|
||||
.failWithCodeNoParameterAddedToErrorCode("transaction.exists.on.date");
|
||||
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
@@ -815,19 +798,67 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
final Money annualFee = Money.of(this.currency, this.annualFeeAmount);
|
||||
SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, transactionDate, annualFee);
|
||||
SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, annualFeeTransactionDate, annualFee);
|
||||
this.transactions.add(annualFeeTransaction);
|
||||
|
||||
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
|
||||
|
||||
final LocalDate today = DateUtils.getLocalDateOfTenant();
|
||||
calculateInterestUsing(mc, today);
|
||||
|
||||
updateAnnualFeeNextDueDate();
|
||||
currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate();
|
||||
if (currentAnnualFeeNextDueDate != null) {
|
||||
LocalDate newAnnualFeeNextDueDate = new LocalDate(currentAnnualFeeNextDueDate).withMonthOfYear(this.annualFeeOnMonth)
|
||||
.withDayOfMonth(this.annualFeeOnDay).plusYears(1);
|
||||
this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate();
|
||||
} else {
|
||||
updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
|
||||
}
|
||||
|
||||
return annualFeeTransaction;
|
||||
}
|
||||
|
||||
private boolean isNotValidAnnualFeeTransactionDate(final LocalDate annualFeeTransactionDate, final LocalDate today) {
|
||||
return !isValidAnnualFeeTransactionDate(annualFeeTransactionDate, today);
|
||||
}
|
||||
|
||||
private boolean isValidAnnualFeeTransactionDate(final LocalDate annualFeeTransactionDate, final LocalDate today) {
|
||||
|
||||
LocalDate startingDate = getActivationLocalDate();
|
||||
boolean isValid = false;
|
||||
while (!startingDate.isAfter(today) && !isValid) {
|
||||
LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay);
|
||||
if (startingDate.isAfter(nextDueLocalDate)) {
|
||||
nextDueLocalDate = nextDueLocalDate.plusYears(1);
|
||||
}
|
||||
isValid = nextDueLocalDate.isEqual(annualFeeTransactionDate);
|
||||
|
||||
startingDate = nextDueLocalDate.plusYears(1);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private void updateToNextAnnualFeeDueDateFrom(final LocalDate startingDate) {
|
||||
LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay);
|
||||
this.annualFeeNextDueDate = nextDueLocalDate.toDate();
|
||||
}
|
||||
|
||||
private LocalDate getNextAnnualFeeDueDate() {
|
||||
LocalDate nextAnnualFeeDueDate = null;
|
||||
if (this.annualFeeNextDueDate != null) {
|
||||
nextAnnualFeeDueDate = new LocalDate(this.annualFeeNextDueDate);
|
||||
}
|
||||
return nextAnnualFeeDueDate;
|
||||
}
|
||||
|
||||
private boolean annualFeeSettingsNotSet() {
|
||||
return !annualFeeSettingsSet();
|
||||
}
|
||||
|
||||
private boolean annualFeeSettingsSet() {
|
||||
return this.annualFeeOnDay != null && this.annualFeeOnMonth != null;
|
||||
}
|
||||
|
||||
private boolean isAutomaticWithdrawalFee() {
|
||||
return this.withdrawalFeeType != null;
|
||||
}
|
||||
@@ -1004,22 +1035,22 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
this.annualFeeAmount = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) {
|
||||
if (command.hasParameter(annualFeeOnMonthDayParamName)) {
|
||||
final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName);
|
||||
final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName);
|
||||
final Integer newValue = monthDay.getDayOfMonth();
|
||||
actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered);
|
||||
actualChanges.put(localeParamName, localeAsInput);
|
||||
this.annualFeeOnDay = newValue;
|
||||
}
|
||||
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
|
||||
if (this.annualFeeOnDay != dayOfMonthValue) {
|
||||
actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered);
|
||||
actualChanges.put(localeParamName, localeAsInput);
|
||||
this.annualFeeOnDay = dayOfMonthValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) {
|
||||
final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName);
|
||||
final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName);
|
||||
final Integer newValue = monthDay.getMonthOfYear();
|
||||
actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered);
|
||||
actualChanges.put(localeParamName, localeAsInput);
|
||||
this.annualFeeOnMonth = newValue;
|
||||
final Integer monthOfYear = monthDay.getMonthOfYear();
|
||||
if (this.annualFeeOnMonth != monthOfYear) {
|
||||
actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered);
|
||||
actualChanges.put(localeParamName, localeAsInput);
|
||||
this.annualFeeOnMonth = monthOfYear;
|
||||
}
|
||||
}
|
||||
|
||||
// set period type to null if frequency is null
|
||||
@@ -1028,10 +1059,6 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
this.annualFeeOnMonth = null;
|
||||
}
|
||||
|
||||
if (this.annualFeeOnDay != null && this.annualFeeOnMonth != null) {
|
||||
updateAnnualFeeNextDueDate();
|
||||
}
|
||||
|
||||
validateLockinDetails();
|
||||
validateWithdrawalFeeDetails();
|
||||
validateAnnualFeeDetails();
|
||||
@@ -1388,12 +1415,45 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
transactionToUndo.reverse();
|
||||
reversedTransactionIds.add(transactionId);
|
||||
|
||||
if (transactionToUndo.isAnnualFee()) {
|
||||
this.annualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate();
|
||||
if (this.annualFeeNextDueDate == null) {
|
||||
updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
|
||||
} else {
|
||||
LocalDate newAnnualFeeNextDueDate = new LocalDate(this.annualFeeNextDueDate).withMonthOfYear(this.annualFeeOnMonth)
|
||||
.withDayOfMonth(this.annualFeeOnDay).plusYears(1);
|
||||
this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate();
|
||||
}
|
||||
}
|
||||
|
||||
calculateInterestUsing(mc, upToInterestCalculationDate);
|
||||
|
||||
validateAccountBalanceDoesNotBecomeNegativeDueToUndoAction(retreiveListOfTransactions());
|
||||
}
|
||||
}
|
||||
|
||||
private Date findLatestAnnualFeeTransactionDueDate() {
|
||||
|
||||
Date nextDueDate = null;
|
||||
|
||||
LocalDate lastAnnualFeeTransactionDate = null;
|
||||
for (SavingsAccountTransaction transaction : retreiveOrderedListOfTransactions()) {
|
||||
if (transaction.isAnnualFeeAndNotReversed()) {
|
||||
if (lastAnnualFeeTransactionDate == null) {
|
||||
lastAnnualFeeTransactionDate = transaction.transactionLocalDate();
|
||||
nextDueDate = lastAnnualFeeTransactionDate.toDate();
|
||||
}
|
||||
|
||||
if (transaction.transactionLocalDate().isAfter(lastAnnualFeeTransactionDate)) {
|
||||
lastAnnualFeeTransactionDate = transaction.transactionLocalDate();
|
||||
nextDueDate = lastAnnualFeeTransactionDate.toDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nextDueDate;
|
||||
}
|
||||
|
||||
public Map<String, Object> rejectApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
|
||||
|
||||
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
|
||||
@@ -1548,6 +1608,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
|
||||
this.activatedBy = currentUser;
|
||||
this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate());
|
||||
|
||||
if (annualFeeSettingsSet()) {
|
||||
updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
|
||||
}
|
||||
|
||||
if (this.client.isActivatedAfter(activationDate)) {
|
||||
|
||||
final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
|
||||
|
||||
+18
-6
@@ -146,16 +146,20 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
|
||||
return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawal();
|
||||
}
|
||||
|
||||
public boolean isInterestPosting() {
|
||||
public boolean isInterestPostingAndNotReversed() {
|
||||
return SavingsAccountTransactionType.fromInt(this.typeOf).isInterestPosting() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isWithdrawalFee() {
|
||||
public boolean isWithdrawalFeeAndNotReversed() {
|
||||
return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawalFee() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isAnnualFeeAndNotReversed() {
|
||||
return isAnnualFee() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isAnnualFee() {
|
||||
return SavingsAccountTransactionType.fromInt(this.typeOf).isAnnualFee() && isNotReversed();
|
||||
return SavingsAccountTransactionType.fromInt(this.typeOf).isAnnualFee();
|
||||
}
|
||||
|
||||
public boolean isNotReversed() {
|
||||
@@ -258,7 +262,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
|
||||
Money endOfDayBalance = openingBalance.copy();
|
||||
if (isDeposit()) {
|
||||
endOfDayBalance = openingBalance.plus(getAmount(currency));
|
||||
} else if (isWithdrawal() || isWithdrawalFee()) {
|
||||
} else if (isWithdrawal() || isWithdrawalFeeAndNotReversed()) {
|
||||
endOfDayBalance = openingBalance.minus(getAmount(currency));
|
||||
}
|
||||
|
||||
@@ -274,7 +278,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
|
||||
Money endOfDayBalance = openingBalance.copy();
|
||||
if (isDeposit()) {
|
||||
endOfDayBalance = openingBalance.plus(getAmount(currency));
|
||||
} else if (isWithdrawal() || isWithdrawalFee()) {
|
||||
} else if (isWithdrawal() || isWithdrawalFeeAndNotReversed()) {
|
||||
endOfDayBalance = openingBalance.minus(getAmount(currency));
|
||||
}
|
||||
|
||||
@@ -298,7 +302,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
|
||||
} else {
|
||||
if (isDeposit()) {
|
||||
endOfDayBalance = openingBalance.plus(getAmount(currency));
|
||||
} else if (isWithdrawal() || isWithdrawalFee()) {
|
||||
} else if (isWithdrawal() || isWithdrawalFeeAndNotReversed()) {
|
||||
endOfDayBalance = openingBalance.minus(getAmount(currency));
|
||||
}
|
||||
}
|
||||
@@ -329,4 +333,12 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
|
||||
public boolean isIdentifiedBy(final Long transactionId) {
|
||||
return this.getId().equals(transactionId);
|
||||
}
|
||||
|
||||
public boolean isCredit() {
|
||||
return isDeposit() || isInterestPostingAndNotReversed();
|
||||
}
|
||||
|
||||
public boolean isDebit() {
|
||||
return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -19,9 +19,9 @@ public class SavingsAccountTransactionComparator implements Comparator<SavingsAc
|
||||
final int comparsion = o1.transactionLocalDate().compareTo(o2.transactionLocalDate());
|
||||
if (comparsion == 0) {
|
||||
// equal transaction dates
|
||||
if (o1.isInterestPosting() && !o2.isInterestPosting()) {
|
||||
if (o1.isInterestPostingAndNotReversed() && !o2.isInterestPostingAndNotReversed()) {
|
||||
compareResult = -1;
|
||||
} else if (!o1.isInterestPosting() && o2.isInterestPosting()) {
|
||||
} else if (!o1.isInterestPostingAndNotReversed() && o2.isInterestPostingAndNotReversed()) {
|
||||
compareResult = 1;
|
||||
} else {
|
||||
compareResult = 0;
|
||||
|
||||
+3
-3
@@ -42,7 +42,7 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
public BigDecimal calculateTotalInterestPosted(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (SavingsAccountTransaction transaction : transactions) {
|
||||
if (transaction.isInterestPosting() && transaction.isNotReversed()) {
|
||||
if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) {
|
||||
total = total.plus(transaction.getAmount(currency));
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
public BigDecimal calculateTotalWithdrawalFees(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (SavingsAccountTransaction transaction : transactions) {
|
||||
if (transaction.isWithdrawalFee() && transaction.isNotReversed()) {
|
||||
if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed()) {
|
||||
total = total.plus(transaction.getAmount(currency));
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
public BigDecimal calculateTotalAnnualFees(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (SavingsAccountTransaction transaction : transactions) {
|
||||
if (transaction.isAnnualFee() && transaction.isNotReversed()) {
|
||||
if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed()) {
|
||||
total = total.plus(transaction.getAmount(currency));
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.savings.handler;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class ApplyAnnualFeeSavingsAccountCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SavingsAccountWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public ApplyAnnualFeeSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
LocalDate annualFeeTransactionDate = command.localDateValueOfParameterNamed("annualFeeTransactionDate");
|
||||
|
||||
return this.writePlatformService.applyAnnualFee(command.getSavingsId(), annualFeeTransactionDate);
|
||||
}
|
||||
}
|
||||
+3
@@ -9,6 +9,7 @@ import java.util.Collection;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.service.Page;
|
||||
import org.mifosplatform.portfolio.group.service.SearchParameters;
|
||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
|
||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
|
||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
|
||||
@@ -23,4 +24,6 @@ public interface SavingsAccountReadPlatformService {
|
||||
SavingsAccountTransactionData retrieveDepositTransactionTemplate(Long savingsId);
|
||||
|
||||
Collection<SavingsAccountTransactionData> retrieveAllTransactions(Long savingsId);
|
||||
|
||||
Collection<SavingsAccountAnnualFeeData> retrieveAccountsWithAnnualFeeDue();
|
||||
}
|
||||
+45
-3
@@ -37,6 +37,7 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT
|
||||
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
|
||||
import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
|
||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountApplicationTimelineData;
|
||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
|
||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData;
|
||||
@@ -63,11 +64,16 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
private final SavingsProductReadPlatformService savingsProductReadPlatformService;
|
||||
private final StaffReadPlatformService staffReadPlatformService;
|
||||
private final SavingsDropdownReadPlatformService dropdownReadPlatformService;
|
||||
|
||||
// mappers
|
||||
private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper;
|
||||
private final SavingsAccountTransactionsMapper transactionsMapper;
|
||||
private final SavingAccountMapper savingAccountMapper;
|
||||
private final SavingsAccountAnnualFeeMapper annualFeeMapper;
|
||||
|
||||
// pagination
|
||||
private final PaginationHelper<SavingsAccountData> paginationHelper = new PaginationHelper<SavingsAccountData>();
|
||||
private final SavingAccountMapper savingAccountMapper = new SavingAccountMapper();
|
||||
|
||||
|
||||
@Autowired
|
||||
public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
|
||||
@@ -83,6 +89,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
this.dropdownReadPlatformService = dropdownReadPlatformService;
|
||||
this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper();
|
||||
this.transactionsMapper = new SavingsAccountTransactionsMapper();
|
||||
this.savingAccountMapper = new SavingAccountMapper();
|
||||
this.annualFeeMapper = new SavingsAccountAnnualFeeMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -476,8 +484,6 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
public SavingsAccountTransactionData retrieveDepositTransactionTemplate(final Long savingsId) {
|
||||
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
|
||||
final String sql = "select " + transactionTemplateMapper.schema() + " where sa.id = ?";
|
||||
|
||||
return this.jdbcTemplate.queryForObject(sql, transactionTemplateMapper, new Object[] { savingsId });
|
||||
@@ -494,6 +500,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId });
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SavingsAccountAnnualFeeData> retrieveAccountsWithAnnualFeeDue() {
|
||||
final String sql = "select " + annualFeeMapper.schema()
|
||||
+ " where sa.annual_fee_next_due_date not null and sa.annual_fee_next_due_date <= NOW()";
|
||||
|
||||
return this.jdbcTemplate.query(sql, this.annualFeeMapper, new Object[] {});
|
||||
}
|
||||
|
||||
private static final class SavingsAccountTransactionsMapper implements RowMapper<SavingsAccountTransactionData> {
|
||||
|
||||
private final String schemaSql;
|
||||
@@ -731,4 +745,32 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
annualFeeOnMonthDay, annualFeeNextDueDate, summary);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SavingsAccountAnnualFeeMapper implements RowMapper<SavingsAccountAnnualFeeData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
public SavingsAccountAnnualFeeMapper() {
|
||||
final StringBuilder sqlBuilder = new StringBuilder(200);
|
||||
sqlBuilder.append("sa.id as id, sa.account_no as accountNo, ");
|
||||
sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate, ");
|
||||
sqlBuilder.append("from m_savings_account sa ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsAccountAnnualFeeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate");
|
||||
|
||||
return SavingsAccountAnnualFeeData.instance(id, accountNo, annualFeeNextDueDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.savings.service;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
@@ -16,7 +17,7 @@ public interface SavingsAccountWritePlatformService {
|
||||
|
||||
CommandProcessingResult withdrawal(Long savingsId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult applyAnnualFee(Long savingsId);
|
||||
CommandProcessingResult applyAnnualFee(Long savingsId, LocalDate annualFeeTransactionDate);
|
||||
|
||||
CommandProcessingResult calculateInterest(Long savingsId);
|
||||
|
||||
|
||||
+6
-6
@@ -205,20 +205,20 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult applyAnnualFee(final Long savingsId) {
|
||||
public CommandProcessingResult applyAnnualFee(final Long savingsId, final LocalDate annualFeeTransactionDate) {
|
||||
|
||||
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
|
||||
|
||||
final LocalDate transactionDate = LocalDate.now();
|
||||
|
||||
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<Long>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
||||
|
||||
final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN);
|
||||
final SavingsAccountTransaction annualFee = account.addAnnualFee(mc, fmt, transactionDate,
|
||||
existingTransactionIds, existingReversedTransactionIds);
|
||||
final MathContext mc = MathContext.DECIMAL64;
|
||||
final LocalDate today = DateUtils.getLocalDateOfTenant();
|
||||
|
||||
final SavingsAccountTransaction annualFee = account.addAnnualFee(mc, fmt, annualFeeTransactionDate, today, existingTransactionIds,
|
||||
existingReversedTransactionIds);
|
||||
final Long transactionId = saveTransactionToGenerateTransactionId(annualFee);
|
||||
this.savingAccountRepository.save(account);
|
||||
|
||||
|
||||
+6
-2
@@ -209,8 +209,12 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
|
||||
if (changes.containsKey(SavingsApiConstants.fieldOfficerIdParamName)) {
|
||||
final Long fieldOfficerId = command.longValueOfParameterNamed(SavingsApiConstants.fieldOfficerIdParamName);
|
||||
final Staff fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId);
|
||||
|
||||
Staff fieldOfficer = null;
|
||||
if (fieldOfficerId != null) {
|
||||
fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId);
|
||||
} else {
|
||||
changes.put(SavingsApiConstants.fieldOfficerIdParamName, "");
|
||||
}
|
||||
account.update(fieldOfficer);
|
||||
}
|
||||
|
||||
|
||||
+15
-10
@@ -5,12 +5,14 @@
|
||||
*/
|
||||
package org.mifosplatform.scheduledjobs.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.service.RoutingDataSourceServiceFactory;
|
||||
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.mifosplatform.infrastructure.jobs.annotation.CronTarget;
|
||||
import org.mifosplatform.infrastructure.jobs.service.JobName;
|
||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
|
||||
import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService;
|
||||
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -23,14 +25,18 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ScheduledJobRunnerServiceImpl.class);
|
||||
|
||||
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
|
||||
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
|
||||
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
public ScheduledJobRunnerServiceImpl(final RoutingDataSourceServiceFactory dataSourceServiceFactory,
|
||||
final SavingsAccountWritePlatformService savingsAccountWritePlatformService) {
|
||||
final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
final SavingsAccountReadPlatformService savingsAccountReadPlatformService) {
|
||||
this.dataSourceServiceFactory = dataSourceServiceFactory;
|
||||
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
|
||||
this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -193,16 +199,15 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
|
||||
@Override
|
||||
@CronTarget(jobName = JobName.APPLY_ANNUAL_FEE_FOR_SAVINGS)
|
||||
public void applyAnnualFeeForSavings() {
|
||||
|
||||
final Collection<SavingsAccountAnnualFeeData> annualFeeData = this.savingsAccountReadPlatformService
|
||||
.retrieveAccountsWithAnnualFeeDue();
|
||||
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
|
||||
|
||||
String sql = "SELECT ms.id as savingsAccountId FROM m_savings_account ms WHERE ms.annual_fee_next_due_date <= NOW() ";
|
||||
List<Long> savingsIds = jdbcTemplate.queryForList(sql, Long.class);
|
||||
|
||||
for (Long savingsId : savingsIds) {
|
||||
this.savingsAccountWritePlatformService.applyAnnualFee(savingsId);
|
||||
for (SavingsAccountAnnualFeeData savingsAccountReference : annualFeeData) {
|
||||
this.savingsAccountWritePlatformService.applyAnnualFee(savingsAccountReference.getId(),
|
||||
savingsAccountReference.getNextAnnualFeeDueDate());
|
||||
}
|
||||
|
||||
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + savingsIds.size());
|
||||
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + annualFeeData.size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user