Added functionality to close a savings account

This commit is contained in:
Pramod Nuthakki
2013-08-26 23:15:53 +05:30
parent 90de8d1cd7
commit 99bddc9f27
11 changed files with 228 additions and 2 deletions
+54
View File
@@ -12704,6 +12704,52 @@ Request Body:
</div>
</div>
<a id="savingsaccounts_close" name="savingsaccounts_close" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Close a savings account</h2>
<p>Results in an Activated savings application being converted into an 'closed' savings account.</p>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=close</code>
<code class="method-request">POST savingsaccount/5?command=close
Content-Type: application/json
Request Body:
{
"dateFormat":"dd MMMM yyyy",
"locale":"en",
"closedOnDate":"26 August 2013",
"note":"close note"
}
</code>
<code class="method-response">
{
"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"
}
}
</code>
</div>
</div>
<a id="savingsaccounts_calculate_interest" name="savingsaccounts_calculate_interest" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
@@ -19323,6 +19369,14 @@ GET https://DomainName/api/v1/search?query=000000111
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>savingsaccounts/{accountId}?command=close</td>
<td><a href="#savingsaccounts_close">Close a savings account</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>savingsaccounts/{accountId}?command=calculateInterest</td>
@@ -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");
@@ -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";
@@ -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());
}
@@ -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<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
monthDayFormatParamName, nameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName,
@@ -150,4 +153,7 @@ public class SavingsApiConstants {
public static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, activatedOnDateParamName));
public static final Set<String> SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, closedOnDateParamName, noteParamName));
}
@@ -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);
@@ -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<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
SavingsApiConstants.SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
@@ -1658,6 +1658,63 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return actualChanges;
}
public Map<String, Object> close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<SavingsAccountTransaction> 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;
@@ -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);
}
}
@@ -28,4 +28,6 @@ public interface SavingsAccountWritePlatformService {
void postInterestForAccounts();
CommandProcessingResult adjustSavingsTransaction(Long savingsId, Long transactionId, JsonCommand command);
CommandProcessingResult close(Long savingsId, JsonCommand command);
}
@@ -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<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> 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();
}
}