support ability to undo transactions on savings accounts.

This commit is contained in:
Keith Woodlock
2013-07-18 16:53:30 +01:00
parent 12abc17a88
commit a3dc8ff378
21 changed files with 266 additions and 51 deletions
+34 -1
View File
@@ -11862,7 +11862,7 @@ Content-Type: application/json
No Request Body: No Request Body:
{ {
"locale": "en", "locale": "en",
"nominalAnnualInterestRate": "5.65", "nominalAnnualInterestRate": "5.9999999999",
} }
</code> </code>
<code class="method-response"> <code class="method-response">
@@ -12053,6 +12053,31 @@ No Request Body:
</code> </code>
</div> </div>
</div> </div>
<a id="savingsaccounts_undotransaction" name="savingsaccounts_undotransaction" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Undo transaction</h2>
<p>This command reverses the given transaction.</p>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{accountsId}/transactions/{transactionId}?command=undo</code>
<code class="method-request">POST savingsaccounts/1/transactions/1?command=undo
Content-Type: application/json
No Request Body:
{
}
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 2,
"savingsId": 1,
"resourceId": 1
}
</code>
</div>
</div>
<!-- end of savings accounts api --> <!-- end of savings accounts api -->
<!-- start of datatables api docs --> <!-- start of datatables api docs -->
@@ -17218,6 +17243,14 @@ GET https://DomainName/api/v1/search?query=000000111
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
</tr>
<tr class="alt">
<td></td>
<td>savingsaccounts/{accountId}/transactions/{transactionId}?command=undo</td>
<td><a href="#savingsaccounts_undotransaction">Undo transaction</a></td>
<td></td>
<td></td>
<td></td>
</tr> </tr>
</table> </table>
</div> </div>
@@ -35,7 +35,7 @@ SET
`approvedon_date`=`activatedon_date`; `approvedon_date`=`activatedon_date`;
INSERT INTO `mifostenant-default`.`m_permission` INSERT INTO `m_permission`
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) (`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
VALUES VALUES
('transaction_savings', 'APPROVE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVE', 1), ('transaction_savings', 'APPROVE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVE', 1),
@@ -44,7 +44,7 @@ VALUES
('transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVALUNDO', 1), ('transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVALUNDO', 1),
('transaction_savings', 'CLOSE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CLOSE', 1); ('transaction_savings', 'CLOSE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CLOSE', 1);
INSERT INTO `mifostenant-default`.`m_permission` INSERT INTO `m_permission`
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) (`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
VALUES VALUES
('transaction_savings', 'APPROVE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVE', 0), ('transaction_savings', 'APPROVE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVE', 0),
@@ -0,0 +1,5 @@
INSERT INTO `m_permission`
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
VALUES
('transaction_savings', 'UNDOTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UNDOTRANSACTION', 1),
('transaction_savings', 'UNDOTRANSACTION_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UNDOTRANSACTION', 0);
@@ -459,6 +459,10 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("POSTINTEREST") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); return this.actionName.equalsIgnoreCase("POSTINTEREST") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
} }
public boolean isSavingsAccountUndoTransaction() {
return this.actionName.equalsIgnoreCase("UNDOTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
public boolean isCalendarResource() { public boolean isCalendarResource() {
return this.entityName.equalsIgnoreCase("CALENDAR"); return this.entityName.equalsIgnoreCase("CALENDAR");
} }
@@ -820,6 +820,17 @@ public class CommandWrapperBuilder {
return this; return this;
} }
public CommandWrapperBuilder undoSavingsAccountTransaction(final Long accountId, final Long transactionId) {
this.actionName = "UNDOTRANSACTION";
this.entityName = "SAVINGSACCOUNT";
this.savingsId = accountId;
this.entityId = accountId;
this.subentityId = transactionId;
this.transactionId = transactionId.toString();
this.href = "/savingsaccounts/" + accountId + "/transactions/" + transactionId + "?command=undo";
return this;
}
public CommandWrapperBuilder savingsAccountInterestCalculation(final Long accountId) { public CommandWrapperBuilder savingsAccountInterestCalculation(final Long accountId) {
this.actionName = "CALCULATEINTEREST"; this.actionName = "CALCULATEINTEREST";
this.entityName = "SAVINGSACCOUNT"; this.entityName = "SAVINGSACCOUNT";
@@ -401,6 +401,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("calculateInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("calculateInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountInterestPosting()) { } else if (wrapper.isSavingsAccountInterestPosting()) {
handler = this.applicationContext.getBean("postInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("postInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountUndoTransaction()) {
handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else { } else {
throw new UnsupportedCommandException(wrapper.commandName()); throw new UnsupportedCommandException(wrapper.commandName());
} }
@@ -80,4 +80,12 @@ public enum SavingsAccountTransactionType {
public boolean isAnnualFee() { public boolean isAnnualFee() {
return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue()); return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue());
} }
public boolean isDebit() {
return isWithdrawal() || isWithdrawalFee() || isAnnualFee();
}
public boolean isCredit() {
return isDeposit() || isInterestPosting();
}
} }
@@ -28,6 +28,7 @@ public class SavingsApiConstants {
public static String activateAction = ".activate"; public static String activateAction = ".activate";
public static String modifyApplicationAction = ".modify"; public static String modifyApplicationAction = ".modify";
public static String deleteApplicationAction = ".delete"; public static String deleteApplicationAction = ".delete";
public static String undoTransactionAction = ".undotransaction";
// general // general
public static final String localeParamName = "locale"; public static final String localeParamName = "locale";
@@ -109,8 +109,36 @@ public class SavingsAccountTransactionsApiResource {
} }
if (result == null) { if (result == null) {
// FIXME - KW - handle blank command param use case //
throw new UnrecognizedQueryParamException("command", commandParam); throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "deposit", "withdrawal" });
}
return this.toApiJsonSerializer.serialize(result);
}
@POST
@Path("{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String transaction(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId,
@QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
String jsonApiRequest = apiRequestBodyAsJson;
if (StringUtils.isBlank(jsonApiRequest)) {
jsonApiRequest = "{}";
}
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
CommandProcessingResult result = null;
if (is(commandParam, "undo")) {
final CommandWrapper commandRequest = builder.undoSavingsAccountTransaction(savingsId, transactionId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
if (result == null) {
//
throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "undo" });
} }
return this.toApiJsonSerializer.serialize(result); return this.toApiJsonSerializer.serialize(result);
@@ -193,7 +193,12 @@ public class SavingsAccountsApiResource {
public String handleCommands(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam, public String handleCommands(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam,
final String apiRequestBodyAsJson) { final String apiRequestBodyAsJson) {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); String jsonApiRequest = apiRequestBodyAsJson;
if (StringUtils.isBlank(jsonApiRequest)) {
jsonApiRequest = "{}";
}
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
CommandProcessingResult result = null; CommandProcessingResult result = null;
if (is(commandParam, "reject")) { if (is(commandParam, "reject")) {
@@ -212,7 +217,7 @@ public class SavingsAccountsApiResource {
final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build(); final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "calculateInterest")) { } else if (is(commandParam, "calculateInterest")) {
final CommandWrapper commandRequest = builder.savingsAccountInterestCalculation(accountId).build(); final CommandWrapper commandRequest = builder.withJson(null).savingsAccountInterestCalculation(accountId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "postInterest")) { } else if (is(commandParam, "postInterest")) {
final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(accountId).build(); final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(accountId).build();
@@ -27,16 +27,18 @@ public class SavingsAccountTransactionData {
private final CurrencyData currency; private final CurrencyData currency;
private final PaymentDetailData paymentDetailData; private final PaymentDetailData paymentDetailData;
private final BigDecimal amount; private final BigDecimal amount;
private final BigDecimal runningBalance;
private final boolean reversed;
// templates // templates
final Collection<CodeValueData> paymentTypeOptions; final Collection<CodeValueData> paymentTypeOptions;
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType, public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount) { final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed) {
final Collection<CodeValueData> paymentTypeOptions = null; final Collection<CodeValueData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
amount, paymentTypeOptions); amount, runningBalance, reversed, paymentTypeOptions);
} }
public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo, public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo,
@@ -44,10 +46,12 @@ public class SavingsAccountTransactionData {
final Long id = null; final Long id = null;
final SavingsAccountTransactionEnumData transactionType = null; final SavingsAccountTransactionEnumData transactionType = null;
final BigDecimal amount = null; final BigDecimal amount = null;
final BigDecimal runningBalance = null;
final boolean reversed = false;
final PaymentDetailData paymentDetailData = null; final PaymentDetailData paymentDetailData = null;
final Collection<CodeValueData> paymentTypeOptions = null; final Collection<CodeValueData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate, return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate,
currency, amount, null); currency, amount, runningBalance, reversed, null);
} }
public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData, public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData,
@@ -55,12 +59,14 @@ public class SavingsAccountTransactionData {
return new SavingsAccountTransactionData(savingsAccountTransactionData.id, savingsAccountTransactionData.transactionType, return new SavingsAccountTransactionData(savingsAccountTransactionData.id, savingsAccountTransactionData.transactionType,
savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId, savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId,
savingsAccountTransactionData.accountNo, savingsAccountTransactionData.date, savingsAccountTransactionData.currency, savingsAccountTransactionData.accountNo, savingsAccountTransactionData.date, savingsAccountTransactionData.currency,
savingsAccountTransactionData.amount, paymentTypeOptions); savingsAccountTransactionData.amount, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed,
paymentTypeOptions);
} }
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final Collection<CodeValueData> paymentTypeOptions) { final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed,
final Collection<CodeValueData> paymentTypeOptions) {
this.id = id; this.id = id;
this.transactionType = transactionType; this.transactionType = transactionType;
this.paymentDetailData = paymentDetailData; this.paymentDetailData = paymentDetailData;
@@ -69,6 +75,8 @@ public class SavingsAccountTransactionData {
this.date = date; this.date = date;
this.currency = currency; this.currency = currency;
this.amount = amount; this.amount = amount;
this.runningBalance = runningBalance;
this.reversed = reversed;
this.paymentTypeOptions = paymentTypeOptions; this.paymentTypeOptions = paymentTypeOptions;
} }
} }
@@ -247,6 +247,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
private boolean accountNumberRequiresAutoGeneration = false; private boolean accountNumberRequiresAutoGeneration = false;
@Transient @Transient
private SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; private SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
@Transient
private SavingsHelper savingsHelper;
protected SavingsAccount() { protected SavingsAccount() {
// //
@@ -330,8 +332,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
* helper services/components used for update summary details after * helper services/components used for update summary details after
* events/transactions on a {@link SavingsAccount}. * events/transactions on a {@link SavingsAccount}.
*/ */
public void setHelpers(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper) { public void setHelpers(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
final SavingsHelper savingsHelper) {
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
this.savingsHelper = savingsHelper;
} }
public boolean isNotActive() { public boolean isNotActive() {
@@ -350,10 +354,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return SavingsAccountStatusType.fromInt(this.status).isSubmittedAndPendingApproval(); return SavingsAccountStatusType.fromInt(this.status).isSubmittedAndPendingApproval();
} }
public void postInterest(final SavingsHelper savingsHelper, final MathContext mc, final LocalDate interestPostingUpToDate, public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final List<Long> existingTransactionIds,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) { final List<Long> existingReversedTransactionIds) {
final List<PostingPeriod> postingPeriods = calculateInterestUsing(savingsHelper, mc, interestPostingUpToDate); final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate);
Money interestPostedToDate = Money.zero(this.currency); Money interestPostedToDate = Money.zero(this.currency);
@@ -431,8 +435,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
* period check if an existing 'interest posting' transaction exists for * period check if an existing 'interest posting' transaction exists for
* date and matches the amount posted * date and matches the amount posted
*/ */
public List<PostingPeriod> calculateInterestUsing(final SavingsHelper savingsHelper, final MathContext mc, public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate) {
final LocalDate upToInterestCalculationDate) {
// no openingBalance concept supported yet but probably will to allow // no openingBalance concept supported yet but probably will to allow
// for migrations. // for migrations.
@@ -732,9 +735,32 @@ public class SavingsAccount extends AbstractPersistable<Long> {
} }
} }
public SavingsAccountTransaction addAnnualFee(final SavingsHelper savingsHelper, final MathContext mc, private void validateAccountBalanceDoesNotBecomeNegativeDueToUndoAction(final List<SavingsAccountTransaction> transactionsSortedByDate) {
final DateTimeFormatter formatter, final LocalDate transactionDate, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) { Money runningBalance = Money.zero(this.currency);
for (SavingsAccountTransaction transaction : transactionsSortedByDate) {
if (transaction.isNotReversed() && transaction.isDeposit() || transaction.isInterestPosting()) {
runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
} else if (transaction.isNotReversed() && transaction.isWithdrawal()) {
runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
}
if (runningBalance.isLessThanZero()) {
//
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.undoTransactionAction);
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.going.negative");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
}
public SavingsAccountTransaction addAnnualFee(final MathContext mc, final DateTimeFormatter formatter, final LocalDate transactionDate,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
if (isNotActive()) { if (isNotActive()) {
@@ -795,7 +821,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
calculateInterestUsing(savingsHelper, mc, today); calculateInterestUsing(mc, today);
updateAnnualFeeNextDueDate(); updateAnnualFeeNextDueDate();
@@ -1345,6 +1371,29 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return actualChanges; return actualChanges;
} }
public void undoTransaction(final Long transactionId, final List<Long> reversedTransactionIds, final MathContext mc,
final LocalDate upToInterestCalculationDate) {
SavingsAccountTransaction transactionToUndo = null;
for (SavingsAccountTransaction transaction : this.transactions) {
if (transaction.isIdentifiedBy(transactionId)) {
transactionToUndo = transaction;
}
}
if (transactionToUndo == null) {
// throw non found exception
} else {
transactionToUndo.reverse();
reversedTransactionIds.add(transactionId);
calculateInterestUsing(mc, upToInterestCalculationDate);
validateAccountBalanceDoesNotBecomeNegativeDueToUndoAction(retreiveListOfTransactions());
}
}
public Map<String, Object> rejectApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { public Map<String, Object> rejectApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
@@ -54,6 +54,7 @@ import com.google.gson.JsonElement;
public class SavingsAccountAssembler { public class SavingsAccountAssembler {
private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
private final SavingsHelper savingsHelper = new SavingsHelper();
private final ClientRepositoryWrapper clientRepository; private final ClientRepositoryWrapper clientRepository;
private final GroupRepositoryWrapper groupRepository; private final GroupRepositoryWrapper groupRepository;
private final StaffRepositoryWrapper staffRepository; private final StaffRepositoryWrapper staffRepository;
@@ -213,7 +214,7 @@ public class SavingsAccountAssembler {
submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, monthDayOfAnnualFee); withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, monthDayOfAnnualFee);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant()); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant());
@@ -222,7 +223,7 @@ public class SavingsAccountAssembler {
public SavingsAccount assembleFrom(final Long savingsId) { public SavingsAccount assembleFrom(final Long savingsId) {
SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId); SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, savingsHelper);
return account; return account;
} }
@@ -25,21 +25,15 @@ import org.springframework.stereotype.Service;
public class SavingsAccountRepositoryWrapper { public class SavingsAccountRepositoryWrapper {
private final SavingsAccountRepository repository; private final SavingsAccountRepository repository;
private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
@Autowired @Autowired
public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository, public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository) {
final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper) {
this.repository = repository; this.repository = repository;
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
} }
public SavingsAccount findOneWithNotFoundDetection(final Long savingsId) { public SavingsAccount findOneWithNotFoundDetection(final Long savingsId) {
final SavingsAccount account = this.repository.findOne(savingsId); final SavingsAccount account = this.repository.findOne(savingsId);
if (account == null) { throw new SavingsAccountNotFoundException(savingsId); } if (account == null) { throw new SavingsAccountNotFoundException(savingsId); }
account.setHelpers(this.savingsAccountTransactionSummaryWrapper);
return account; return account;
} }
@@ -325,4 +325,8 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate());
return balanceInterval.containsPortionOf(periodInterval); return balanceInterval.containsPortionOf(periodInterval);
} }
public boolean isIdentifiedBy(final Long transactionId) {
return this.getId().equals(transactionId);
}
} }
@@ -11,4 +11,6 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SavingsAccountTransactionRepository extends JpaRepository<SavingsAccountTransaction, Long>, public interface SavingsAccountTransactionRepository extends JpaRepository<SavingsAccountTransaction, Long>,
JpaSpecificationExecutor<SavingsAccountTransaction> { JpaSpecificationExecutor<SavingsAccountTransaction> {
SavingsAccountTransaction findOneByIdAndSavingsAccountId(Long transactionId, Long savingsId);
} }
@@ -0,0 +1,32 @@
/**
* 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.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 UndoTransactionSavingsAccountCommandHandler implements NewCommandSourceHandler {
private final SavingsAccountWritePlatformService writePlatformService;
@Autowired
public UndoTransactionSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
final Long transactionId = Long.valueOf(command.getTransactionId());
return this.writePlatformService.undoTransaction(command.getSavingsId(), transactionId);
}
}
@@ -489,8 +489,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
@Override @Override
public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final Long savingsId) { public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final Long savingsId) {
final String sql = "select " + this.transactionsMapper.schema() final String sql = "select " + this.transactionsMapper.schema() + " where sa.id = ? order by tr.transaction_date DESC, tr.id DESC";
+ " where sa.id = ? and tr.is_reversed=0 order by tr.transaction_date DESC, tr.id DESC";
return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId }); return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId });
} }
@@ -504,7 +503,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final StringBuilder sqlBuilder = new StringBuilder(400); final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, "); sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,"); sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
sqlBuilder.append("sa.id as savingsId, sa.account_no as accountNo, "); sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,");
sqlBuilder.append("sa.id as savingsId, sa.account_no as accountNo,");
sqlBuilder.append("pd.payment_type_cv_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "); sqlBuilder.append("pd.payment_type_cv_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, ");
sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "); sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, ");
sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, "); sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, ");
@@ -514,7 +514,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("from m_savings_account sa "); sqlBuilder.append("from m_savings_account sa ");
sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id "); sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id ");
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
sqlBuilder.append("left JOIN m_payment_detail pd ON tr.payment_detail_id = pd.id "); sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id ");
sqlBuilder.append("left join m_code_value cv on pd.payment_type_cv_id = cv.id "); sqlBuilder.append("left join m_code_value cv on pd.payment_type_cv_id = cv.id ");
this.schemaSql = sqlBuilder.toString(); this.schemaSql = sqlBuilder.toString();
@@ -532,6 +532,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate"); final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount"); final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance");
final boolean reversed = rs.getBoolean("reversed");
final Long savingsId = rs.getLong("savingsId"); final Long savingsId = rs.getLong("savingsId");
final String accountNo = rs.getString("accountNo"); final String accountNo = rs.getString("accountNo");
@@ -561,7 +563,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
currencyNameCode); currencyNameCode);
return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency,
amount); amount, runningBalance, reversed);
} }
} }
@@ -21,4 +21,6 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult calculateInterest(Long savingsId); CommandProcessingResult calculateInterest(Long savingsId);
CommandProcessingResult postInterest(Long savingsId); CommandProcessingResult postInterest(Long savingsId);
CommandProcessingResult undoTransaction(Long savingsId, Long transactionId);
} }
@@ -34,7 +34,6 @@ import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepositoryWrapper; import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionRepository; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.mifosplatform.portfolio.savings.domain.SavingsHelper;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -52,8 +51,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final SavingsHelper savingsHelper = new SavingsHelper();
@Autowired @Autowired
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final SavingsAccountRepositoryWrapper savingAccountRepository, final SavingsAccountRepositoryWrapper savingAccountRepository,
@@ -131,10 +128,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (account.isBeforeLastPostingPeriod(transactionDate)) { if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(this.savingsHelper, mc, today, existingTransactionIds, existingReversedTransactionIds); account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(this.savingsHelper, mc, today); account.calculateInterestUsing(mc, today);
} }
final Long transactionId = saveTransactionToGenerateTransactionId(deposit); final Long transactionId = saveTransactionToGenerateTransactionId(deposit);
@@ -185,10 +182,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (account.isBeforeLastPostingPeriod(transactionDate)) { if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(this.savingsHelper, mc, today, existingTransactionIds, existingReversedTransactionIds); account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(this.savingsHelper, mc, today); account.calculateInterestUsing(mc, today);
} }
final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal); final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal);
@@ -220,7 +217,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN);
final SavingsAccountTransaction annualFee = account.addAnnualFee(this.savingsHelper, mc, fmt, transactionDate, final SavingsAccountTransaction annualFee = account.addAnnualFee(mc, fmt, transactionDate,
existingTransactionIds, existingReversedTransactionIds); existingTransactionIds, existingReversedTransactionIds);
final Long transactionId = saveTransactionToGenerateTransactionId(annualFee); final Long transactionId = saveTransactionToGenerateTransactionId(annualFee);
this.savingAccountRepository.save(account); this.savingAccountRepository.save(account);
@@ -239,15 +236,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Transactional @Transactional
@Override @Override
public CommandProcessingResult calculateInterest(final Long savingsId) { public CommandProcessingResult calculateInterest(final Long savingsId) {
this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN); final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN);
account.calculateInterestUsing(this.savingsHelper, mc, today); account.calculateInterestUsing(mc, today);
this.savingAccountRepository.save(account); this.savingAccountRepository.save(account);
@@ -263,7 +258,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Transactional @Transactional
@Override @Override
public CommandProcessingResult postInterest(final Long savingsId) { public CommandProcessingResult postInterest(final Long savingsId) {
this.context.authenticatedUser();
final List<Long> existingTransactionIds = new ArrayList<Long>(); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
@@ -272,7 +267,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN);
account.postInterest(this.savingsHelper, mc, today, existingTransactionIds, existingReversedTransactionIds); account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
this.savingAccountRepository.save(account); this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
@@ -286,6 +281,35 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.build(); .build();
} }
@Override
public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId) {
final List<Long> newTransactionIds = new ArrayList<Long>();
final List<Long> reversedTransactionIds = new ArrayList<Long>();
SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository.findOneByIdAndSavingsAccountId(
transactionId, savingsId);
if (savingsAccountTransaction == null) {
// throw new not found exception
}
final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
account.undoTransaction(transactionId, reversedTransactionIds, mc, today);
postJournalEntries(account, newTransactionIds, reversedTransactionIds);
return new CommandProcessingResultBuilder() //
.withEntityId(savingsId) //
.withOfficeId(account.officeId()) //
.withClientId(account.clientId()) //
.withGroupId(account.groupId()) //
.withSavingsId(savingsId) //
.build();
}
private void postJournalEntries(final SavingsAccount savingsAccount, final List<Long> existingTransactionIds, private void postJournalEntries(final SavingsAccount savingsAccount, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) { final List<Long> existingReversedTransactionIds) {
@@ -169,7 +169,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final Map<String, Object> changes = new LinkedHashMap<String, Object>(20); final Map<String, Object> changes = new LinkedHashMap<String, Object>(20);
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
account.modifyApplication(command, changes); account.modifyApplication(command, changes);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant()); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant());
@@ -236,7 +236,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
@Override @Override
public CommandProcessingResult deleteApplication(final Long savingsId) { public CommandProcessingResult deleteApplication(final Long savingsId) {
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
if (account.isNotSubmittedAndPendingApproval()) { if (account.isNotSubmittedAndPendingApproval()) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();