feat: implement full current account transaction write service
- New CurrentAccountTransactionWritePlatformService interface with deposit, withdrawal, holdAmount, releaseAmount, undoTransaction, reverseTransaction, and adjustTransaction operations - New CurrentAccountTransactionWritePlatformServiceImpl using m_current_account tables directly (not m_savings_account), with GL journal entry posting - Add holdAmount, releaseAmount, reversal factory methods to CurrentAccountTransaction - Wire all 7 current account transaction command handlers to new write service - Fix CurrentAccountTransactionsApiResource template SQL column: currency_multiplesof - Fix account number search in SearchParameters Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
b25d5a30d2
commit
ce0fb18eeb
+1
@@ -44,6 +44,7 @@ public class SearchParameters {
|
||||
private String orderBy;
|
||||
private String sortOrder;
|
||||
private String accountNo;
|
||||
private Integer depositType;
|
||||
private String currencyCode;
|
||||
private Long staffId;
|
||||
private Long loanId;
|
||||
|
||||
+44
@@ -146,4 +146,48 @@ public class CurrentAccountTransaction extends AbstractPersistableCustom<Long> {
|
||||
txn.submittedOnDate = transactionDate;
|
||||
return txn;
|
||||
}
|
||||
|
||||
// transactionTypeEnum = 8 (Hold Amount)
|
||||
public static CurrentAccountTransaction holdAmount(CurrentAccount account, Office office,
|
||||
BigDecimal amount, LocalDate transactionDate, String reasonForBlock) {
|
||||
CurrentAccountTransaction txn = new CurrentAccountTransaction();
|
||||
txn.currentAccount = account;
|
||||
txn.office = office;
|
||||
txn.transactionTypeEnum = 8;
|
||||
txn.amount = amount;
|
||||
txn.transactionDate = transactionDate;
|
||||
txn.lienTransaction = true;
|
||||
txn.reasonForBlock = reasonForBlock;
|
||||
txn.submittedOnDate = transactionDate;
|
||||
return txn;
|
||||
}
|
||||
|
||||
// transactionTypeEnum = 9 (Release Amount)
|
||||
public static CurrentAccountTransaction releaseAmount(CurrentAccount account, Office office,
|
||||
BigDecimal amount, LocalDate transactionDate, Long holdTransactionId) {
|
||||
CurrentAccountTransaction txn = new CurrentAccountTransaction();
|
||||
txn.currentAccount = account;
|
||||
txn.office = office;
|
||||
txn.transactionTypeEnum = 9;
|
||||
txn.amount = amount;
|
||||
txn.transactionDate = transactionDate;
|
||||
txn.releaseIdOfHoldAmountTransaction = holdTransactionId;
|
||||
txn.submittedOnDate = transactionDate;
|
||||
return txn;
|
||||
}
|
||||
|
||||
// transactionTypeEnum = 7 (Reversal / Undo)
|
||||
public static CurrentAccountTransaction reversal(CurrentAccount account, Office office,
|
||||
BigDecimal amount, LocalDate transactionDate, Long originalTransactionId) {
|
||||
CurrentAccountTransaction txn = new CurrentAccountTransaction();
|
||||
txn.currentAccount = account;
|
||||
txn.office = office;
|
||||
txn.transactionTypeEnum = 7;
|
||||
txn.amount = amount;
|
||||
txn.transactionDate = transactionDate;
|
||||
txn.reversal = true;
|
||||
txn.originalTransactionId = originalTransactionId;
|
||||
txn.submittedOnDate = transactionDate;
|
||||
return txn;
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -26,4 +26,6 @@ public interface CurrentAccountReadPlatformService {
|
||||
Map<String, Object> retrieveOne(Long accountId);
|
||||
|
||||
List<Map<String, Object>> retrieveAll();
|
||||
|
||||
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.currentaccount.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface CurrentAccountTransactionWritePlatformService {
|
||||
|
||||
CommandProcessingResult deposit(Long accountId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult withdrawal(Long accountId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult holdAmount(Long accountId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult releaseAmount(Long accountId, Long transactionId);
|
||||
|
||||
CommandProcessingResult undoTransaction(Long accountId, Long transactionId, boolean isReversal);
|
||||
|
||||
CommandProcessingResult reverseTransaction(Long accountId, Long transactionId, boolean isReversal, JsonCommand command);
|
||||
|
||||
CommandProcessingResult adjustTransaction(Long accountId, Long transactionId, JsonCommand command);
|
||||
}
|
||||
+57
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@@ -32,6 +34,10 @@ import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
@@ -43,10 +49,14 @@ import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamE
|
||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
|
||||
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService;
|
||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/currentaccounts/{accountId}/transactions")
|
||||
@@ -55,11 +65,58 @@ import org.springframework.stereotype.Component;
|
||||
@RequiredArgsConstructor
|
||||
public class CurrentAccountTransactionsApiResource {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final DefaultToApiJsonSerializer<SavingsAccountTransactionData> toApiJsonSerializer;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
|
||||
private final PaymentTypeReadService paymentTypeReadService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@GET
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve current account transaction template")
|
||||
@ApiResponse(responseCode = "200", description = "OK")
|
||||
public String retrieveTemplate(@PathParam("accountId") final Long accountId, @Context final UriInfo uriInfo) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final String sql = "SELECT ca.currency_code, ca.currency_digits, ca.currency_multiplesof, "
|
||||
+ "mc.display_symbol, mc.name AS currency_name, mc.internationalized_name_code "
|
||||
+ "FROM m_current_account ca "
|
||||
+ "LEFT JOIN m_currency mc ON mc.code = ca.currency_code "
|
||||
+ "WHERE ca.id = ?";
|
||||
|
||||
final Map<String, Object> template;
|
||||
try {
|
||||
template = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
|
||||
final Map<String, Object> map = new LinkedHashMap<>();
|
||||
final LocalDate today = LocalDate.now();
|
||||
map.put("date", new int[] { today.getYear(), today.getMonthValue(), today.getDayOfMonth() });
|
||||
|
||||
final Map<String, Object> currency = new LinkedHashMap<>();
|
||||
currency.put("code", rs.getString("currency_code"));
|
||||
currency.put("decimalPlaces", rs.getInt("currency_digits"));
|
||||
currency.put("inMultiplesOf", rs.getInt("currency_multiplesof"));
|
||||
currency.put("displaySymbol", rs.getString("display_symbol"));
|
||||
currency.put("name", rs.getString("currency_name"));
|
||||
currency.put("nameCode", rs.getString("internationalized_name_code"));
|
||||
map.put("currency", currency);
|
||||
return map;
|
||||
}, accountId);
|
||||
} catch (org.springframework.dao.EmptyResultDataAccessException e) {
|
||||
throw new CurrentAccountNotFoundException(accountId);
|
||||
}
|
||||
|
||||
final Collection<PaymentTypeData> paymentTypeOptions = paymentTypeReadService.retrieveAllPaymentTypes();
|
||||
template.put("paymentTypeOptions", paymentTypeOptions);
|
||||
|
||||
return GSON.toJson(template);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{transactionId}")
|
||||
|
||||
+4
-2
@@ -101,10 +101,12 @@ public class CurrentAccountsApiResource {
|
||||
@QueryParam("offset") final Integer offset,
|
||||
@QueryParam("limit") final Integer limit,
|
||||
@QueryParam("orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") final String sortOrder) {
|
||||
@QueryParam("sortOrder") final String sortOrder,
|
||||
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
|
||||
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
|
||||
final List<Map<String, Object>> accounts = currentAccountReadPlatformService.retrieveAll();
|
||||
final List<Map<String, Object>> accounts = currentAccountReadPlatformService.retrieveAll(offset, limit, accountNo, clientName);
|
||||
return GSON.toJson(accounts);
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -159,7 +159,9 @@ public class FixedDepositAccountsApiResource {
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
|
||||
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
|
||||
sqlValidator.validate(orderBy);
|
||||
@@ -170,7 +172,7 @@ public class FixedDepositAccountsApiResource {
|
||||
|
||||
if (paginationParameters.isPaged()) {
|
||||
final Page<DepositAccountData> account = this.depositAccountReadPlatformService
|
||||
.retrieveAllPaged(DepositAccountType.FIXED_DEPOSIT, paginationParameters);
|
||||
.retrieveAllPaged(DepositAccountType.FIXED_DEPOSIT, paginationParameters, accountNo, clientName);
|
||||
return this.toApiJsonSerializer.serialize(settings, account,
|
||||
DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
+4
-2
@@ -140,7 +140,9 @@ public class RecurringDepositAccountsApiResource {
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
|
||||
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
|
||||
sqlValidator.validate(orderBy);
|
||||
@@ -151,7 +153,7 @@ public class RecurringDepositAccountsApiResource {
|
||||
|
||||
if (paginationParameters.isPaged()) {
|
||||
final Page<DepositAccountData> account = this.depositAccountReadPlatformService
|
||||
.retrieveAllPaged(DepositAccountType.RECURRING_DEPOSIT, paginationParameters);
|
||||
.retrieveAllPaged(DepositAccountType.RECURRING_DEPOSIT, paginationParameters, accountNo, clientName);
|
||||
return this.toApiJsonSerializer.serialize(settings, account,
|
||||
DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
+5
-2
@@ -130,7 +130,10 @@ public class SavingsAccountsApiResource {
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
|
||||
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
|
||||
@QueryParam("depositType") @Parameter(description = "depositType") final Integer depositType) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
@@ -138,7 +141,7 @@ public class SavingsAccountsApiResource {
|
||||
sqlValidator.validate(sortOrder);
|
||||
sqlValidator.validate(externalId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
.orderBy(orderBy).sortOrder(sortOrder).accountNo(accountNo).name(clientName).depositType(depositType).build();
|
||||
|
||||
final Page<SavingsAccountData> products = savingsAccountReadPlatformService.retrieveAll(searchParameters);
|
||||
|
||||
|
||||
+4
-4
@@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@CommandType(entity = "CURRENTACCOUNT", action = "ADJUSTTRANSACTION")
|
||||
public class CurrentAccountTransactionAdjustmentCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SavingsAccountWritePlatformService writePlatformService;
|
||||
private final CurrentAccountTransactionWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CurrentAccountTransactionAdjustmentCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
|
||||
public CurrentAccountTransactionAdjustmentCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,6 @@ public class CurrentAccountTransactionAdjustmentCommandHandler implements NewCom
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
final Long transactionId = Long.valueOf(command.getTransactionId());
|
||||
return this.writePlatformService.adjustSavingsTransaction(command.getSavingsId(), transactionId, command);
|
||||
return this.writePlatformService.adjustTransaction(command.getSavingsId(), transactionId, command);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@CommandType(entity = "CURRENTACCOUNT", action = "DEPOSIT")
|
||||
public class DepositCurrentAccountCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SavingsAccountWritePlatformService writePlatformService;
|
||||
private final CurrentAccountTransactionWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public DepositCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
|
||||
public DepositCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@CommandType(entity = "CURRENTACCOUNT", action = "HOLDAMOUNT")
|
||||
public class HoldAmountCurrentAccountCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SavingsAccountWritePlatformService writePlatformService;
|
||||
private final CurrentAccountTransactionWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public HoldAmountCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
|
||||
public HoldAmountCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@CommandType(entity = "CURRENTACCOUNT", action = "RELEASEAMOUNT")
|
||||
public class ReleaseAmountCurrentAccountCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SavingsAccountWritePlatformService writePlatformService;
|
||||
private final CurrentAccountTransactionWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public ReleaseAmountCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
|
||||
public ReleaseAmountCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@CommandType(entity = "CURRENTACCOUNT", action = "REVERSETRANSACTION")
|
||||
public class ReverseTransactionCurrentAccountCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SavingsAccountWritePlatformService writePlatformService;
|
||||
private final CurrentAccountTransactionWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public ReverseTransactionCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
|
||||
public ReverseTransactionCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@CommandType(entity = "CURRENTACCOUNT", action = "UNDOTRANSACTION")
|
||||
public class UndoTransactionCurrentAccountCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SavingsAccountWritePlatformService writePlatformService;
|
||||
private final CurrentAccountTransactionWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UndoTransactionCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
|
||||
public UndoTransactionCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@CommandType(entity = "CURRENTACCOUNT", action = "WITHDRAWAL")
|
||||
public class WithdrawCurrentAccountCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SavingsAccountWritePlatformService writePlatformService;
|
||||
private final CurrentAccountTransactionWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public WithdrawCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) {
|
||||
public WithdrawCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
|
||||
+40
@@ -27,6 +27,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
@@ -226,6 +227,45 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> retrieveAll(final Integer offset, final Integer limit, final String accountNo,
|
||||
final String clientName) {
|
||||
final StringBuilder sqlBuilder = new StringBuilder(BASE_SQL);
|
||||
final List<Object> params = new ArrayList<>();
|
||||
boolean hasWhere = false;
|
||||
|
||||
if (StringUtils.isNotBlank(accountNo)) {
|
||||
sqlBuilder.append(hasWhere ? " AND " : " WHERE ");
|
||||
sqlBuilder.append("ca.account_no LIKE ?");
|
||||
params.add("%" + accountNo + "%");
|
||||
hasWhere = true;
|
||||
}
|
||||
if (StringUtils.isNotBlank(clientName)) {
|
||||
sqlBuilder.append(hasWhere ? " AND " : " WHERE ");
|
||||
sqlBuilder.append("c.display_name LIKE ?");
|
||||
params.add("%" + clientName + "%");
|
||||
hasWhere = true;
|
||||
}
|
||||
if (limit != null && limit > 0) {
|
||||
sqlBuilder.append(" LIMIT ?");
|
||||
params.add(limit);
|
||||
if (offset != null && offset >= 0) {
|
||||
sqlBuilder.append(" OFFSET ?");
|
||||
params.add(offset);
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, Object>> rows = jdbcTemplate.query(sqlBuilder.toString(), new FlatRowMapper(), params.toArray());
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Map<String, Object> row : rows) {
|
||||
final Map<String, Object> response = buildStructuredResponse(row);
|
||||
final Long id = ((Number) row.get("id")).longValue();
|
||||
response.put("charges", retrieveCharges(id));
|
||||
result.add(response);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildStructuredResponse(Map<String, Object> row) {
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
|
||||
|
||||
+468
@@ -0,0 +1,468 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.PortfolioProductType;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository;
|
||||
import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CurrentAccountTransactionWritePlatformServiceImpl implements CurrentAccountTransactionWritePlatformService {
|
||||
|
||||
// Savings entity type used for GL journal entries (EntityType.SAVINGS = 2)
|
||||
private static final Integer ENTITY_TYPE_SAVINGS = 2;
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final CurrentAccountRepository currentAccountRepository;
|
||||
private final CurrentAccountTransactionRepository currentAccountTransactionRepository;
|
||||
private final CurrentProductRepository currentProductRepository;
|
||||
private final ProductToGLAccountMappingRepository productToGLAccountMappingRepository;
|
||||
private final JournalEntryRepository journalEntryRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult deposit(final Long accountId, final JsonCommand command) {
|
||||
context.authenticatedUser();
|
||||
|
||||
final CurrentAccount account = findActiveAccount(accountId);
|
||||
final Office office = account.getOffice();
|
||||
|
||||
LocalDate txnDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
if (txnDate == null) txnDate = LocalDate.now();
|
||||
|
||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
validatePositiveAmount(amount, accountId, "deposit");
|
||||
|
||||
final String refNo = command.stringValueOfParameterNamed("accountNumber");
|
||||
|
||||
final CurrentAccountTransaction txn = CurrentAccountTransaction.deposit(account, office, amount, txnDate, refNo);
|
||||
|
||||
final BigDecimal oldBalance = safeBalance(account.getAccountBalanceDerived());
|
||||
final BigDecimal newBalance = oldBalance.add(amount);
|
||||
txn.setRunningBalanceDerived(newBalance);
|
||||
|
||||
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||
|
||||
account.setAccountBalanceDerived(newBalance);
|
||||
account.setTotalDepositsDerived(safeBalance(account.getTotalDepositsDerived()).add(amount));
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
postTransactionGl(account, txn, JournalEntryType.DEBIT, JournalEntryType.CREDIT, "Current Account Deposit");
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withEntityId(txn.getId())
|
||||
.withSavingsId(accountId)
|
||||
.withClientId(account.getClient() != null ? account.getClient().getId() : null)
|
||||
.withOfficeId(office != null ? office.getId() : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult withdrawal(final Long accountId, final JsonCommand command) {
|
||||
context.authenticatedUser();
|
||||
|
||||
final CurrentAccount account = findActiveAccount(accountId);
|
||||
final Office office = account.getOffice();
|
||||
|
||||
LocalDate txnDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
if (txnDate == null) txnDate = LocalDate.now();
|
||||
|
||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
validatePositiveAmount(amount, accountId, "withdrawal");
|
||||
|
||||
final BigDecimal currentBalance = safeBalance(account.getAccountBalanceDerived());
|
||||
final BigDecimal onHold = safeBalance(account.getOnHoldFundsDerived());
|
||||
final BigDecimal available = currentBalance.subtract(onHold);
|
||||
|
||||
if (!account.isAllowOverdraft() && amount.compareTo(available) > 0) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.insufficient.balance",
|
||||
"Insufficient available balance. Available: " + available + ", Requested: " + amount, accountId);
|
||||
}
|
||||
|
||||
final String refNo = command.stringValueOfParameterNamed("accountNumber");
|
||||
|
||||
final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, office, amount, txnDate, refNo);
|
||||
|
||||
final BigDecimal newBalance = currentBalance.subtract(amount);
|
||||
txn.setRunningBalanceDerived(newBalance);
|
||||
|
||||
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||
|
||||
account.setAccountBalanceDerived(newBalance);
|
||||
account.setTotalWithdrawalsDerived(safeBalance(account.getTotalWithdrawalsDerived()).add(amount));
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
// Withdrawal GL: DR SAVINGS_CONTROL (liability down), CR SAVINGS_REFERENCE (cash out)
|
||||
postTransactionGl(account, txn, JournalEntryType.CREDIT, JournalEntryType.DEBIT, "Current Account Withdrawal");
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withEntityId(txn.getId())
|
||||
.withSavingsId(accountId)
|
||||
.withClientId(account.getClient() != null ? account.getClient().getId() : null)
|
||||
.withOfficeId(office != null ? office.getId() : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult holdAmount(final Long accountId, final JsonCommand command) {
|
||||
context.authenticatedUser();
|
||||
|
||||
final CurrentAccount account = findActiveAccount(accountId);
|
||||
final Office office = account.getOffice();
|
||||
|
||||
LocalDate txnDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
if (txnDate == null) txnDate = LocalDate.now();
|
||||
|
||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
validatePositiveAmount(amount, accountId, "hold amount");
|
||||
|
||||
final BigDecimal currentBalance = safeBalance(account.getAccountBalanceDerived());
|
||||
final BigDecimal onHold = safeBalance(account.getOnHoldFundsDerived());
|
||||
final BigDecimal available = currentBalance.subtract(onHold);
|
||||
|
||||
if (amount.compareTo(available) > 0) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.insufficient.balance.for.hold",
|
||||
"Insufficient available balance to place hold. Available: " + available + ", Requested: " + amount, accountId);
|
||||
}
|
||||
|
||||
final String reason = command.stringValueOfParameterNamed("reasonForBlock");
|
||||
|
||||
final CurrentAccountTransaction txn = CurrentAccountTransaction.holdAmount(account, office, amount, txnDate, reason);
|
||||
txn.setRunningBalanceDerived(currentBalance);
|
||||
|
||||
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||
|
||||
account.setOnHoldFundsDerived(onHold.add(amount));
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withEntityId(txn.getId())
|
||||
.withSavingsId(accountId)
|
||||
.withClientId(account.getClient() != null ? account.getClient().getId() : null)
|
||||
.withOfficeId(office != null ? office.getId() : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult releaseAmount(final Long accountId, final Long transactionId) {
|
||||
context.authenticatedUser();
|
||||
|
||||
final CurrentAccount account = findActiveAccount(accountId);
|
||||
final Office office = account.getOffice();
|
||||
|
||||
final CurrentAccountTransaction holdTxn = currentAccountTransactionRepository.findById(transactionId)
|
||||
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||
"error.msg.current.account.transaction.not.found",
|
||||
"Hold transaction not found: " + transactionId, transactionId));
|
||||
|
||||
if (!holdTxn.getCurrentAccount().getId().equals(accountId)) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.not.found",
|
||||
"Transaction does not belong to this account.", transactionId);
|
||||
}
|
||||
|
||||
if (holdTxn.isReversed()) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.already.reversed",
|
||||
"Hold transaction is already released/reversed.", transactionId);
|
||||
}
|
||||
|
||||
final BigDecimal holdAmount = holdTxn.getAmount();
|
||||
final BigDecimal currentBalance = safeBalance(account.getAccountBalanceDerived());
|
||||
|
||||
// Mark hold transaction as reversed
|
||||
holdTxn.setReversed(true);
|
||||
currentAccountTransactionRepository.save(holdTxn);
|
||||
|
||||
// Create release transaction
|
||||
final CurrentAccountTransaction releaseTxn = CurrentAccountTransaction.releaseAmount(
|
||||
account, office, holdAmount, LocalDate.now(), transactionId);
|
||||
releaseTxn.setRunningBalanceDerived(currentBalance);
|
||||
currentAccountTransactionRepository.saveAndFlush(releaseTxn);
|
||||
|
||||
// Reduce onHold
|
||||
final BigDecimal onHold = safeBalance(account.getOnHoldFundsDerived());
|
||||
account.setOnHoldFundsDerived(onHold.subtract(holdAmount).max(BigDecimal.ZERO));
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withEntityId(releaseTxn.getId())
|
||||
.withSavingsId(accountId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult undoTransaction(final Long accountId, final Long transactionId, final boolean isReversal) {
|
||||
context.authenticatedUser();
|
||||
|
||||
final CurrentAccount account = findActiveAccount(accountId);
|
||||
|
||||
final CurrentAccountTransaction txn = currentAccountTransactionRepository.findById(transactionId)
|
||||
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||
"error.msg.current.account.transaction.not.found",
|
||||
"Transaction not found: " + transactionId, transactionId));
|
||||
|
||||
if (!txn.getCurrentAccount().getId().equals(accountId)) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.not.found",
|
||||
"Transaction does not belong to this account.", transactionId);
|
||||
}
|
||||
|
||||
if (txn.isReversed()) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.already.reversed",
|
||||
"Transaction is already reversed.", transactionId);
|
||||
}
|
||||
|
||||
reverseTransactionBalances(account, txn);
|
||||
|
||||
txn.setReversed(true);
|
||||
currentAccountTransactionRepository.save(txn);
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withEntityId(transactionId)
|
||||
.withSavingsId(accountId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult reverseTransaction(final Long accountId, final Long transactionId,
|
||||
final boolean isReversal, final JsonCommand command) {
|
||||
context.authenticatedUser();
|
||||
|
||||
final CurrentAccount account = findActiveAccount(accountId);
|
||||
|
||||
final CurrentAccountTransaction original = currentAccountTransactionRepository.findById(transactionId)
|
||||
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||
"error.msg.current.account.transaction.not.found",
|
||||
"Transaction not found: " + transactionId, transactionId));
|
||||
|
||||
if (!original.getCurrentAccount().getId().equals(accountId)) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.not.found",
|
||||
"Transaction does not belong to this account.", transactionId);
|
||||
}
|
||||
|
||||
if (original.isReversed()) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.already.reversed",
|
||||
"Transaction is already reversed.", transactionId);
|
||||
}
|
||||
|
||||
reverseTransactionBalances(account, original);
|
||||
original.setReversed(true);
|
||||
currentAccountTransactionRepository.save(original);
|
||||
|
||||
// Create a reversal entry
|
||||
final CurrentAccountTransaction reversalTxn = CurrentAccountTransaction.reversal(
|
||||
account, account.getOffice(), original.getAmount(), LocalDate.now(), transactionId);
|
||||
reversalTxn.setRunningBalanceDerived(safeBalance(account.getAccountBalanceDerived()));
|
||||
currentAccountTransactionRepository.saveAndFlush(reversalTxn);
|
||||
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withEntityId(reversalTxn.getId())
|
||||
.withSavingsId(accountId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult adjustTransaction(final Long accountId, final Long transactionId,
|
||||
final JsonCommand command) {
|
||||
context.authenticatedUser();
|
||||
|
||||
final CurrentAccount account = findActiveAccount(accountId);
|
||||
final Office office = account.getOffice();
|
||||
|
||||
final CurrentAccountTransaction original = currentAccountTransactionRepository.findById(transactionId)
|
||||
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||
"error.msg.current.account.transaction.not.found",
|
||||
"Transaction not found: " + transactionId, transactionId));
|
||||
|
||||
if (!original.getCurrentAccount().getId().equals(accountId)) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.not.found",
|
||||
"Transaction does not belong to this account.", transactionId);
|
||||
}
|
||||
|
||||
if (original.isReversed()) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.already.reversed",
|
||||
"Transaction is already reversed.", transactionId);
|
||||
}
|
||||
|
||||
final BigDecimal newAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
validatePositiveAmount(newAmount, accountId, "adjust");
|
||||
|
||||
LocalDate newDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
if (newDate == null) newDate = original.getTransactionDate();
|
||||
|
||||
// Undo original
|
||||
reverseTransactionBalances(account, original);
|
||||
original.setReversed(true);
|
||||
currentAccountTransactionRepository.save(original);
|
||||
|
||||
// Create adjusted transaction of the same type
|
||||
final CurrentAccountTransaction adjusted;
|
||||
if (original.getTransactionTypeEnum() == 1) {
|
||||
adjusted = CurrentAccountTransaction.deposit(account, office, newAmount, newDate,
|
||||
command.stringValueOfParameterNamed("accountNumber"));
|
||||
} else {
|
||||
adjusted = CurrentAccountTransaction.withdrawal(account, office, newAmount, newDate,
|
||||
command.stringValueOfParameterNamed("accountNumber"));
|
||||
}
|
||||
adjusted.setOriginalTransactionId(transactionId);
|
||||
adjusted.setRunningBalanceDerived(safeBalance(account.getAccountBalanceDerived()));
|
||||
currentAccountTransactionRepository.saveAndFlush(adjusted);
|
||||
|
||||
// Apply new transaction to balance
|
||||
applyTransactionToBalance(account, adjusted);
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withEntityId(adjusted.getId())
|
||||
.withSavingsId(accountId)
|
||||
.build();
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private CurrentAccount findActiveAccount(final Long accountId) {
|
||||
final CurrentAccount account = currentAccountRepository.findById(accountId)
|
||||
.orElseThrow(() -> new CurrentAccountNotFoundException(accountId));
|
||||
if (!Integer.valueOf(300).equals(account.getStatusEnum())) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.current.account.not.active",
|
||||
"Current account is not in Active state.", accountId);
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
private void validatePositiveAmount(final BigDecimal amount, final Long accountId, final String operation) {
|
||||
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new GeneralPlatformDomainRuleException(
|
||||
"error.msg.current.account." + operation + ".invalid.amount",
|
||||
"Transaction amount must be greater than zero for " + operation + ".", accountId);
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal safeBalance(final BigDecimal value) {
|
||||
return value != null ? value : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private void reverseTransactionBalances(final CurrentAccount account, final CurrentAccountTransaction txn) {
|
||||
final BigDecimal amount = txn.getAmount();
|
||||
final Integer type = txn.getTransactionTypeEnum();
|
||||
|
||||
if (Integer.valueOf(1).equals(type)) {
|
||||
// Was a deposit: reverse it
|
||||
account.setAccountBalanceDerived(safeBalance(account.getAccountBalanceDerived()).subtract(amount));
|
||||
account.setTotalDepositsDerived(safeBalance(account.getTotalDepositsDerived()).subtract(amount).max(BigDecimal.ZERO));
|
||||
} else if (Integer.valueOf(2).equals(type)) {
|
||||
// Was a withdrawal: reverse it
|
||||
account.setAccountBalanceDerived(safeBalance(account.getAccountBalanceDerived()).add(amount));
|
||||
account.setTotalWithdrawalsDerived(safeBalance(account.getTotalWithdrawalsDerived()).subtract(amount).max(BigDecimal.ZERO));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyTransactionToBalance(final CurrentAccount account, final CurrentAccountTransaction txn) {
|
||||
final BigDecimal amount = txn.getAmount();
|
||||
final Integer type = txn.getTransactionTypeEnum();
|
||||
|
||||
if (Integer.valueOf(1).equals(type)) {
|
||||
account.setAccountBalanceDerived(safeBalance(account.getAccountBalanceDerived()).add(amount));
|
||||
account.setTotalDepositsDerived(safeBalance(account.getTotalDepositsDerived()).add(amount));
|
||||
} else if (Integer.valueOf(2).equals(type)) {
|
||||
account.setAccountBalanceDerived(safeBalance(account.getAccountBalanceDerived()).subtract(amount));
|
||||
account.setTotalWithdrawalsDerived(safeBalance(account.getTotalWithdrawalsDerived()).add(amount));
|
||||
}
|
||||
}
|
||||
|
||||
// GL: cashEntryType=DEBIT/CREDIT for SAVINGS_REFERENCE; controlEntryType for SAVINGS_CONTROL
|
||||
private void postTransactionGl(final CurrentAccount account, final CurrentAccountTransaction txn,
|
||||
final JournalEntryType cashEntryType, final JournalEntryType controlEntryType, final String description) {
|
||||
try {
|
||||
final CurrentProduct product = currentProductRepository.findById(account.getProductId()).orElse(null);
|
||||
if (product == null) return;
|
||||
final Integer accountingType = product.getAccountingType();
|
||||
if (accountingType == null || accountingType == 1) return; // NONE = 1
|
||||
|
||||
final GLAccount cashAccount = findGlAccount(account.getProductId(),
|
||||
CashAccountsForSavings.SAVINGS_REFERENCE.getValue());
|
||||
final GLAccount controlAccount = findGlAccount(account.getProductId(),
|
||||
CashAccountsForSavings.SAVINGS_CONTROL.getValue());
|
||||
|
||||
if (cashAccount == null || controlAccount == null) {
|
||||
log.warn("[GL] Missing GL account mapping for current account product id={}", account.getProductId());
|
||||
return;
|
||||
}
|
||||
|
||||
final String ref = "CAT" + txn.getId();
|
||||
postGlEntry(account, cashAccount, cashEntryType, txn.getAmount(), txn.getTransactionDate(), ref, description);
|
||||
postGlEntry(account, controlAccount, controlEntryType, txn.getAmount(), txn.getTransactionDate(), ref, description);
|
||||
} catch (Exception e) {
|
||||
log.error("[GL] Failed GL posting for CA transaction id={}: {}", txn.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private GLAccount findGlAccount(final Long productId, final int financialAccountType) {
|
||||
final var mapping = productToGLAccountMappingRepository
|
||||
.findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(
|
||||
productId, PortfolioProductType.SAVING.getValue(), financialAccountType, null);
|
||||
return mapping != null ? mapping.getGlAccount() : null;
|
||||
}
|
||||
|
||||
private void postGlEntry(final CurrentAccount account, final GLAccount glAccount,
|
||||
final JournalEntryType entryType, final BigDecimal amount,
|
||||
final LocalDate date, final String transactionRef, final String description) {
|
||||
final JournalEntry entry = JournalEntry.createNew(
|
||||
account.getOffice(), null, glAccount,
|
||||
account.getCurrencyCode(), transactionRef,
|
||||
false, date, entryType, amount,
|
||||
description,
|
||||
ENTITY_TYPE_SAVINGS, account.getId(),
|
||||
null, null, null, null, null);
|
||||
journalEntryRepository.save(entry);
|
||||
}
|
||||
}
|
||||
+3
@@ -33,6 +33,9 @@ public interface DepositAccountReadPlatformService {
|
||||
|
||||
Page<DepositAccountData> retrieveAllPaged(DepositAccountType depositAccountType, PaginationParameters paginationParameters);
|
||||
|
||||
Page<DepositAccountData> retrieveAllPaged(DepositAccountType depositAccountType, PaginationParameters paginationParameters,
|
||||
String accountNo, String clientName);
|
||||
|
||||
Collection<DepositAccountData> retrieveAllForLookup(DepositAccountType depositAccountType);
|
||||
|
||||
DepositAccountData retrieveOne(DepositAccountType depositAccountType, Long accountId);
|
||||
|
||||
+34
@@ -29,9 +29,11 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
@@ -182,6 +184,38 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
depositAccountMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DepositAccountData> retrieveAllPaged(final DepositAccountType depositAccountType,
|
||||
final PaginationParameters paginationParameters, final String accountNo, final String clientName) {
|
||||
|
||||
this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues,
|
||||
depositAccountType.resourceName());
|
||||
|
||||
final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType);
|
||||
if (depositAccountMapper == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
|
||||
sqlBuilder.append(depositAccountMapper.schema());
|
||||
sqlBuilder.append(" where sa.deposit_type_enum = ? ");
|
||||
|
||||
final List<Object> params = new ArrayList<>();
|
||||
params.add(depositAccountType.getValue());
|
||||
if (StringUtils.isNotBlank(accountNo)) {
|
||||
sqlBuilder.append(" and sa.account_no like ? ");
|
||||
params.add("%" + accountNo + "%");
|
||||
}
|
||||
if (StringUtils.isNotBlank(clientName)) {
|
||||
sqlBuilder.append(" and c.display_name like ? ");
|
||||
params.add("%" + clientName + "%");
|
||||
}
|
||||
sqlBuilder.append(paginationParameters.paginationSql());
|
||||
|
||||
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), params.toArray(), depositAccountMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DepositAccountData> retrieveAllForLookup(final DepositAccountType depositAccountType) {
|
||||
|
||||
|
||||
+13
-1
@@ -166,7 +166,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
sqlBuilder.append(" join m_office o on o.id = c.office_id");
|
||||
sqlBuilder.append(" where o.hierarchy like ?");
|
||||
|
||||
final Object[] objectArray = new Object[3];
|
||||
final Object[] objectArray = new Object[10];
|
||||
objectArray[0] = hierarchySearchString;
|
||||
int arrayPos = 1;
|
||||
if (searchParameters != null) {
|
||||
@@ -186,6 +186,18 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
sqlBuilder.append(" and c.office_id = ?");
|
||||
objectArray[arrayPos++] = searchParameters.getOfficeId();
|
||||
}
|
||||
if (StringUtils.isNotBlank(searchParameters.getAccountNo())) {
|
||||
sqlBuilder.append(" and sa.account_no like ?");
|
||||
objectArray[arrayPos++] = "%" + searchParameters.getAccountNo() + "%";
|
||||
}
|
||||
if (StringUtils.isNotBlank(searchParameters.getName())) {
|
||||
sqlBuilder.append(" and c.display_name like ?");
|
||||
objectArray[arrayPos++] = "%" + searchParameters.getName() + "%";
|
||||
}
|
||||
if (searchParameters.getDepositType() != null) {
|
||||
sqlBuilder.append(" and sa.deposit_type_enum = ?");
|
||||
objectArray[arrayPos++] = searchParameters.getDepositType();
|
||||
}
|
||||
if (searchParameters.hasOrderBy()) {
|
||||
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
|
||||
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
|
||||
|
||||
Reference in New Issue
Block a user