diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 34b8af6db..eeb1df727 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -12704,6 +12704,52 @@ Request Body: +   +
+
+

Close a savings account

+

Results in an Activated savings application being converted into an 'closed' savings account.

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=close + POST savingsaccount/5?command=close +Content-Type: application/json +Request Body: + { + "dateFormat":"dd MMMM yyyy", + "locale":"en", + "closedOnDate":"26 August 2013", + "note":"close note" + } + + +{ +"officeId":1, +"clientId":1, +"savingsId":5, +"resourceId":5, +"changes":{ + "status":{ + "id":600, + "code":"savingsAccountStatusType.closed", + "value":"Closed", + "submittedAndPendingApproval":false, + "approved":false, + "rejected":false, + "withdrawnByApplicant":false, + "active":false, + "closed":true + }, + "locale":"en", + "dateFormat":"dd MMMM yyyy", + "closedOnDate":"26 August 2013", + "note":"close note" + } +} + +
+
+  
@@ -19323,6 +19369,14 @@ GET https://DomainName/api/v1/search?query=000000111 + + + savingsaccounts/{accountId}?command=close + Close a savings account + + + + savingsaccounts/{accountId}?command=calculateInterest diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 90ff1c732..4798d0f7e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -457,6 +457,10 @@ public class CommandWrapper { public boolean isSavingsAccountDeposit() { return this.actionName.equalsIgnoreCase("DEPOSIT") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); } + + public boolean isSavingsAccountClose() { + return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); + } public boolean isSavingsAccountWithdrawal() { return this.actionName.equalsIgnoreCase("WITHDRAWAL") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index 49d7838ad..c3dd77033 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -816,6 +816,16 @@ public class CommandWrapperBuilder { this.href = "/savingsaccounts/" + accountId + "?command=activate"; return this; } + + public CommandWrapperBuilder closeSavingsAccountApplication(final Long accountId) { + this.actionName = "CLOSE"; + this.entityName = "SAVINGSACCOUNT"; + this.entityId = accountId; + this.savingsId = accountId; + this.href = "/savingsaccounts/" + accountId + "?command=close"; + return this; + } + public CommandWrapperBuilder createAccountTransfer() { this.actionName = "CREATE"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index b8822b31c..dd4a87290 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -416,6 +416,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isAdjustSavingsAccountTransaction()) { handler = this.applicationContext.getBean("savingsTransactionAdjustmentCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isSavingsAccountClose()) { + handler = this.applicationContext.getBean("closeSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else { throw new UnsupportedCommandException(wrapper.commandName()); } 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 a3f919e3e..b0452a62d 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 @@ -31,6 +31,7 @@ public class SavingsApiConstants { public static String undoTransactionAction = ".undotransaction"; public static String applyAnnualFeeTransactionAction = ".applyannualfee"; public static String adjustTransactionAction = ".adjusttransaction"; + public static String closeAction = ".close"; // command public static String COMMAND_UNDO_TRANSACTION = "undo"; @@ -91,6 +92,8 @@ public class SavingsApiConstants { public static final String runningBalanceParamName = "runningBalance"; public static final String reversedParamName = "reversed"; public static final String dateParamName = "date"; + + public static final String noteParamName = "note"; public static final Set SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, monthDayFormatParamName, nameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, @@ -150,4 +153,7 @@ public class SavingsApiConstants { public static final Set SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName)); + + public static final Set SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, + dateFormatParamName, closedOnDateParamName, noteParamName)); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java index 6a5239575..a1074510a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java @@ -224,12 +224,15 @@ public class SavingsAccountsApiResource { } else if (is(commandParam, "applyAnnualFees")) { final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(accountId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "close")) { + final CommandWrapper commandRequest = builder.closeSavingsAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { // throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "reject", "withdrawnByApplicant", "approve", - "undoapproval", "activate", "calculateInterest", "postInterest" }); + "undoapproval", "activate", "calculateInterest", "postInterest", "close" }); } return this.toApiJsonSerializer.serialize(result); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java index 3ce43134a..4db68311d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.savings.data; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.activatedOnDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.bankNumberParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.checkNumberParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.closedOnDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.paymentTypeIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.receiptNumberParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.routingCodeParamName; @@ -105,6 +106,28 @@ public class SavingsAccountTransactionDataValidator { throwExceptionIfValidationWarningsExist(dataValidationErrors); } + + public void validateClosing(final JsonCommand command) { + final String json = command.json(); + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + SavingsApiConstants.SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); + + final JsonElement element = command.parsedJson(); + + final LocalDate activationDate = fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element); + baseDataValidator.reset().parameter(closedOnDateParamName).value(activationDate).notNull(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { 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 906e87f70..2537667d9 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 @@ -1658,6 +1658,63 @@ public class SavingsAccount extends AbstractPersistable { return actualChanges; } + public Map close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { + + final Map actualChanges = new LinkedHashMap(); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (closedDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + if (closedDate.isAfter(tenantsTodayDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + List savingsAccountTransactions = retreiveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + } + if (getAccountBalance().doubleValue() > 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.not.zero"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + this.status = SavingsAccountStatusType.CLOSED.getValue(); + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate.toDate(); + this.closedBy = currentUser; + + return actualChanges; + } + private Date calculateDateAccountIsLockedUntil(final LocalDate activationLocalDate) { Date lockedInUntilLocalDate = null; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CloseSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CloseSavingsAccountCommandHandler.java new file mode 100755 index 000000000..221782427 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CloseSavingsAccountCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.savings.handler; + +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 CloseSavingsAccountCommandHandler implements NewCommandSourceHandler{ + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public CloseSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { + this.writePlatformService = savingAccountWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.close(command.getSavingsId(), command); + } + +} 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 283c358ab..b649dd5b1 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 @@ -28,4 +28,6 @@ public interface SavingsAccountWritePlatformService { void postInterestForAccounts(); CommandProcessingResult adjustSavingsTransaction(Long savingsId, Long transactionId, JsonCommand command); + + CommandProcessingResult close(Long savingsId, JsonCommand command); } \ 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 1bc9b4562..b355d18fa 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 @@ -14,6 +14,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; @@ -32,6 +33,8 @@ import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; +import org.mifosplatform.portfolio.note.domain.Note; +import org.mifosplatform.portfolio.note.domain.NoteRepository; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.mifosplatform.portfolio.savings.SavingsApiConstants; @@ -62,6 +65,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final SavingsAccountDomainService savingsAccountDomainService; + private final NoteRepository noteRepository; @Autowired public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -72,7 +76,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final PaymentDetailWritePlatformService paymentDetailWritePlatformService, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final JournalEntryWritePlatformService journalEntryWritePlatformService, - final SavingsAccountDomainService savingsAccountDomainService) { + final SavingsAccountDomainService savingsAccountDomainService, final NoteRepository noteRepository) { this.context = context; this.savingAccountRepository = savingAccountRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; @@ -82,6 +86,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.savingsAccountDomainService = savingsAccountDomainService; + this.noteRepository = noteRepository; } @Transactional @@ -389,4 +394,37 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); } } } + + @Override + public CommandProcessingResult close(Long savingsId, JsonCommand command) { + final AppUser user = this.context.authenticatedUser(); + + this.savingsAccountTransactionDataValidator.validateClosing(command); + + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + final Map changes = account.close(user, command, DateUtils.getLocalDateOfTenant()); + if (!changes.isEmpty()) { + this.savingAccountRepository.save(account); + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + final Note note = Note.savingNote(account, noteText); + changes.put("note", noteText); + this.noteRepository.save(note); + } + + } + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + + return new CommandProcessingResultBuilder() // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); + } } \ No newline at end of file