diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index 75227bd99..46bb779c5 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -11862,7 +11862,7 @@ Content-Type: application/json
No Request Body:
{
"locale": "en",
- "nominalAnnualInterestRate": "5.65",
+ "nominalAnnualInterestRate": "5.9999999999",
}
@@ -12053,6 +12053,31 @@ No Request Body:
+
+
+
+
+
Undo transaction
+
This command reverses the given transaction.
+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{accountsId}/transactions/{transactionId}?command=undo
+ POST savingsaccounts/1/transactions/1?command=undo
+Content-Type: application/json
+No Request Body:
+{
+}
+
+
+{
+ "officeId": 1,
+ "clientId": 2,
+ "savingsId": 1,
+ "resourceId": 1
+}
+
+
+
@@ -17218,6 +17243,14 @@ GET https://DomainName/api/v1/search?query=000000111
|
|
|
+
+
+ |
+ savingsaccounts/{accountId}/transactions/{transactionId}?command=undo |
+ Undo transaction |
+ |
+ |
+ |
diff --git a/mifosng-db/migrations/core_db/V81__savings_related_changes.sql b/mifosng-db/migrations/core_db/V81__savings_related_changes.sql
index 5cf68d763..ee81e1a56 100644
--- a/mifosng-db/migrations/core_db/V81__savings_related_changes.sql
+++ b/mifosng-db/migrations/core_db/V81__savings_related_changes.sql
@@ -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),
diff --git a/mifosng-db/migrations/core_db/V84__undo_savings_transaction_permission.sql b/mifosng-db/migrations/core_db/V84__undo_savings_transaction_permission.sql
new file mode 100644
index 000000000..16860a41c
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V84__undo_savings_transaction_permission.sql
@@ -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);
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
index 17b2ca473..7406539d2 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
@@ -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");
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
index 45db68066..c32081ad0 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
@@ -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";
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
index 68c98a282..0e007a2c2 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
@@ -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());
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java
index fb22b500a..e660f11ca 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java
@@ -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();
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java
index baa0f9d04..d8f136f29 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java
@@ -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";
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java
index 47ecd2bb1..f7a6820f6 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java
@@ -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);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java
index 14f7dab0b..a890c7879 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java
@@ -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();
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java
index 200e84e1f..28547379e 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java
@@ -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 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 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 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 paymentTypeOptions) {
+ final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed,
+ final Collection 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;
}
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java
index 380a0d114..dc08d3320 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java
@@ -247,6 +247,8 @@ public class SavingsAccount extends AbstractPersistable {
private boolean accountNumberRequiresAutoGeneration = false;
@Transient
private SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
+ @Transient
+ private SavingsHelper savingsHelper;
protected SavingsAccount() {
//
@@ -330,8 +332,10 @@ public class SavingsAccount extends AbstractPersistable {
* 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 {
return SavingsAccountStatusType.fromInt(this.status).isSubmittedAndPendingApproval();
}
- public void postInterest(final SavingsHelper savingsHelper, final MathContext mc, final LocalDate interestPostingUpToDate,
- final List existingTransactionIds, final List existingReversedTransactionIds) {
+ public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final List existingTransactionIds,
+ final List existingReversedTransactionIds) {
- final List postingPeriods = calculateInterestUsing(savingsHelper, mc, interestPostingUpToDate);
+ final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate);
Money interestPostedToDate = Money.zero(this.currency);
@@ -431,8 +435,7 @@ public class SavingsAccount extends AbstractPersistable {
* period check if an existing 'interest posting' transaction exists for
* date and matches the amount posted
*/
- public List calculateInterestUsing(final SavingsHelper savingsHelper, final MathContext mc,
- final LocalDate upToInterestCalculationDate) {
+ public List 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 {
}
}
- public SavingsAccountTransaction addAnnualFee(final SavingsHelper savingsHelper, final MathContext mc,
- final DateTimeFormatter formatter, final LocalDate transactionDate, final List existingTransactionIds,
- final List existingReversedTransactionIds) {
+ private void validateAccountBalanceDoesNotBecomeNegativeDueToUndoAction(final List 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 dataValidationErrors = new ArrayList();
+ 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 existingTransactionIds, final List existingReversedTransactionIds) {
if (isNotActive()) {
@@ -795,7 +821,7 @@ public class SavingsAccount extends AbstractPersistable {
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 {
return actualChanges;
}
+ public void undoTransaction(final Long transactionId, final List 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 rejectApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
final Map actualChanges = new LinkedHashMap();
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java
index 2e954d19c..0b0826f29 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java
@@ -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;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
index 711ca69a9..c7d651f8d 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
@@ -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;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java
index 687014b9c..d133d76b1 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java
@@ -325,4 +325,8 @@ public final class SavingsAccountTransaction extends AbstractPersistable {
LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate());
return balanceInterval.containsPortionOf(periodInterval);
}
+
+ public boolean isIdentifiedBy(final Long transactionId) {
+ return this.getId().equals(transactionId);
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionRepository.java
index edd2f8a58..745814a4c 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionRepository.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionRepository.java
@@ -11,4 +11,6 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SavingsAccountTransactionRepository extends JpaRepository,
JpaSpecificationExecutor {
+ SavingsAccountTransaction findOneByIdAndSavingsAccountId(Long transactionId, Long savingsId);
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java
new file mode 100644
index 000000000..a942e2be5
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index 481794968..b54dff26d 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -489,8 +489,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
@Override
public Collection 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);
}
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java
index d6b3b50d6..fc45e279c 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java
@@ -21,4 +21,6 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult calculateInterest(Long savingsId);
CommandProcessingResult postInterest(Long savingsId);
+
+ CommandProcessingResult undoTransaction(Long savingsId, Long transactionId);
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
index dac0e508b..2b973dd7d 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
@@ -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 existingReversedTransactionIds = new ArrayList();
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 existingTransactionIds = new ArrayList();
final List existingReversedTransactionIds = new ArrayList();
@@ -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 newTransactionIds = new ArrayList();
+ final List reversedTransactionIds = new ArrayList();
+
+ 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 existingTransactionIds,
final List existingReversedTransactionIds) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java
index 213580cd3..e20e89165 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java
@@ -169,7 +169,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final Map changes = new LinkedHashMap(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 dataValidationErrors = new ArrayList();