support ability to undo transactions on savings accounts.
This commit is contained in:
+34
-1
@@ -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"> </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");
|
||||
}
|
||||
|
||||
+11
@@ -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";
|
||||
|
||||
+2
@@ -401,6 +401,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
handler = this.applicationContext.getBean("calculateInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isSavingsAccountInterestPosting()) {
|
||||
handler = this.applicationContext.getBean("postInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isSavingsAccountUndoTransaction()) {
|
||||
handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class);
|
||||
} else {
|
||||
throw new UnsupportedCommandException(wrapper.commandName());
|
||||
}
|
||||
|
||||
+8
@@ -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();
|
||||
}
|
||||
}
|
||||
+1
@@ -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";
|
||||
|
||||
+30
-2
@@ -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);
|
||||
|
||||
+7
-2
@@ -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();
|
||||
|
||||
+13
-5
@@ -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;
|
||||
}
|
||||
}
|
||||
+59
-10
@@ -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>();
|
||||
|
||||
+3
-2
@@ -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;
|
||||
}
|
||||
|
||||
+1
-7
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
@@ -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);
|
||||
|
||||
}
|
||||
+32
@@ -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);
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -21,4 +21,6 @@ public interface SavingsAccountWritePlatformService {
|
||||
CommandProcessingResult calculateInterest(Long savingsId);
|
||||
|
||||
CommandProcessingResult postInterest(Long savingsId);
|
||||
|
||||
CommandProcessingResult undoTransaction(Long savingsId, Long transactionId);
|
||||
}
|
||||
+37
-13
@@ -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) {
|
||||
|
||||
|
||||
+2
-2
@@ -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>();
|
||||
|
||||
Reference in New Issue
Block a user