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:
{
"locale": "en",
"nominalAnnualInterestRate": "5.65",
"nominalAnnualInterestRate": "5.9999999999",
}
</code>
<code class="method-response">
@@ -12053,6 +12053,31 @@ No Request Body:
</code>
</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 -->
<!-- start of datatables api docs -->
@@ -17218,6 +17243,14 @@ GET https://DomainName/api/v1/search?query=000000111
<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>
</table>
</div>
@@ -35,7 +35,7 @@ SET
`approvedon_date`=`activatedon_date`;
INSERT INTO `mifostenant-default`.`m_permission`
INSERT INTO `m_permission`
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
VALUES
('transaction_savings', 'APPROVE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVE', 1),
@@ -44,7 +44,7 @@ VALUES
('transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVALUNDO', 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`)
VALUES
('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");
}
public boolean isSavingsAccountUndoTransaction() {
return this.actionName.equalsIgnoreCase("UNDOTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
public boolean isCalendarResource() {
return this.entityName.equalsIgnoreCase("CALENDAR");
}
@@ -820,6 +820,17 @@ public class CommandWrapperBuilder {
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) {
this.actionName = "CALCULATEINTEREST";
this.entityName = "SAVINGSACCOUNT";
@@ -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.isSavingsAccountUndoTransaction()) {
handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -80,4 +80,12 @@ public enum SavingsAccountTransactionType {
public boolean isAnnualFee() {
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 modifyApplicationAction = ".modify";
public static String deleteApplicationAction = ".delete";
public static String undoTransactionAction = ".undotransaction";
// general
public static final String localeParamName = "locale";
@@ -109,8 +109,36 @@ public class SavingsAccountTransactionsApiResource {
}
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);
@@ -193,7 +193,12 @@ public class SavingsAccountsApiResource {
public String handleCommands(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam,
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;
if (is(commandParam, "reject")) {
@@ -212,7 +217,7 @@ public class SavingsAccountsApiResource {
final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} 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);
} else if (is(commandParam, "postInterest")) {
final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(accountId).build();
@@ -27,16 +27,18 @@ public class SavingsAccountTransactionData {
private final CurrencyData currency;
private final PaymentDetailData paymentDetailData;
private final BigDecimal amount;
private final BigDecimal runningBalance;
private final boolean reversed;
// templates
final Collection<CodeValueData> paymentTypeOptions;
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
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;
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,
@@ -44,10 +46,12 @@ public class SavingsAccountTransactionData {
final Long id = null;
final SavingsAccountTransactionEnumData transactionType = null;
final BigDecimal amount = null;
final BigDecimal runningBalance = null;
final boolean reversed = false;
final PaymentDetailData paymentDetailData = null;
final Collection<CodeValueData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate,
currency, amount, null);
currency, amount, runningBalance, reversed, null);
}
public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData,
@@ -55,12 +59,14 @@ public class SavingsAccountTransactionData {
return new SavingsAccountTransactionData(savingsAccountTransactionData.id, savingsAccountTransactionData.transactionType,
savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId,
savingsAccountTransactionData.accountNo, savingsAccountTransactionData.date, savingsAccountTransactionData.currency,
savingsAccountTransactionData.amount, paymentTypeOptions);
savingsAccountTransactionData.amount, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed,
paymentTypeOptions);
}
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
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.transactionType = transactionType;
this.paymentDetailData = paymentDetailData;
@@ -69,6 +75,8 @@ public class SavingsAccountTransactionData {
this.date = date;
this.currency = currency;
this.amount = amount;
this.runningBalance = runningBalance;
this.reversed = reversed;
this.paymentTypeOptions = paymentTypeOptions;
}
}
@@ -247,6 +247,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
private boolean accountNumberRequiresAutoGeneration = false;
@Transient
private SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
@Transient
private SavingsHelper savingsHelper;
protected SavingsAccount() {
//
@@ -330,8 +332,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
* helper services/components used for update summary details after
* 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.savingsHelper = savingsHelper;
}
public boolean isNotActive() {
@@ -350,10 +354,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return SavingsAccountStatusType.fromInt(this.status).isSubmittedAndPendingApproval();
}
public void postInterest(final SavingsHelper savingsHelper, final MathContext mc, final LocalDate interestPostingUpToDate,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final List<Long> existingTransactionIds,
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);
@@ -431,8 +435,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
* period check if an existing 'interest posting' transaction exists for
* date and matches the amount posted
*/
public List<PostingPeriod> calculateInterestUsing(final SavingsHelper savingsHelper, final MathContext mc,
final LocalDate upToInterestCalculationDate) {
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate) {
// no openingBalance concept supported yet but probably will to allow
// for migrations.
@@ -732,9 +735,32 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
}
public SavingsAccountTransaction addAnnualFee(final SavingsHelper savingsHelper, final MathContext mc,
final DateTimeFormatter formatter, final LocalDate transactionDate, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
private void validateAccountBalanceDoesNotBecomeNegativeDueToUndoAction(final List<SavingsAccountTransaction> transactionsSortedByDate) {
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()) {
@@ -795,7 +821,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
final LocalDate today = DateUtils.getLocalDateOfTenant();
calculateInterestUsing(savingsHelper, mc, today);
calculateInterestUsing(mc, today);
updateAnnualFeeNextDueDate();
@@ -1345,6 +1371,29 @@ public class SavingsAccount extends AbstractPersistable<Long> {
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) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
@@ -54,6 +54,7 @@ import com.google.gson.JsonElement;
public class SavingsAccountAssembler {
private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
private final SavingsHelper savingsHelper = new SavingsHelper();
private final ClientRepositoryWrapper clientRepository;
private final GroupRepositoryWrapper groupRepository;
private final StaffRepositoryWrapper staffRepository;
@@ -213,7 +214,7 @@ public class SavingsAccountAssembler {
submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, monthDayOfAnnualFee);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant());
@@ -222,7 +223,7 @@ public class SavingsAccountAssembler {
public SavingsAccount assembleFrom(final Long savingsId) {
SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, savingsHelper);
return account;
}
@@ -25,21 +25,15 @@ import org.springframework.stereotype.Service;
public class SavingsAccountRepositoryWrapper {
private final SavingsAccountRepository repository;
private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
@Autowired
public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository,
final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper) {
public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository) {
this.repository = repository;
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
}
public SavingsAccount findOneWithNotFoundDetection(final Long savingsId) {
final SavingsAccount account = this.repository.findOne(savingsId);
if (account == null) { throw new SavingsAccountNotFoundException(savingsId); }
account.setHelpers(this.savingsAccountTransactionSummaryWrapper);
return account;
}
@@ -325,4 +325,8 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate());
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>,
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
public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final Long savingsId) {
final String sql = "select " + this.transactionsMapper.schema()
+ " where sa.id = ? and tr.is_reversed=0 order by tr.transaction_date DESC, tr.id DESC";
final String sql = "select " + this.transactionsMapper.schema() + " where sa.id = ? order by tr.transaction_date DESC, tr.id DESC";
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);
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("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.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, ");
@@ -514,7 +514,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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_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 ");
this.schemaSql = sqlBuilder.toString();
@@ -532,6 +532,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
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 String accountNo = rs.getString("accountNo");
@@ -561,7 +563,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
currencyNameCode);
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 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.SavingsAccountTransaction;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.mifosplatform.portfolio.savings.domain.SavingsHelper;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -52,8 +51,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final SavingsHelper savingsHelper = new SavingsHelper();
@Autowired
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final SavingsAccountRepositoryWrapper savingAccountRepository,
@@ -131,10 +128,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(this.savingsHelper, mc, today, existingTransactionIds, existingReversedTransactionIds);
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(this.savingsHelper, mc, today);
account.calculateInterestUsing(mc, today);
}
final Long transactionId = saveTransactionToGenerateTransactionId(deposit);
@@ -185,10 +182,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(this.savingsHelper, mc, today, existingTransactionIds, existingReversedTransactionIds);
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(this.savingsHelper, mc, today);
account.calculateInterestUsing(mc, today);
}
final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal);
@@ -220,7 +217,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
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);
final Long transactionId = saveTransactionToGenerateTransactionId(annualFee);
this.savingAccountRepository.save(account);
@@ -239,15 +236,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Transactional
@Override
public CommandProcessingResult calculateInterest(final Long savingsId) {
this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN);
account.calculateInterestUsing(this.savingsHelper, mc, today);
account.calculateInterestUsing(mc, today);
this.savingAccountRepository.save(account);
@@ -263,7 +258,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Transactional
@Override
public CommandProcessingResult postInterest(final Long savingsId) {
this.context.authenticatedUser();
final List<Long> existingTransactionIds = 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 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);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
@@ -286,6 +281,35 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.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,
final List<Long> existingReversedTransactionIds) {
@@ -169,7 +169,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
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.validateNewApplicationState(DateUtils.getLocalDateOfTenant());
@@ -236,7 +236,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
@Override
public CommandProcessingResult deleteApplication(final Long savingsId) {
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
if (account.isNotSubmittedAndPendingApproval()) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();