MIFOSX-432: ability to transfer from savings account to savings account.

This commit is contained in:
Keith Woodlock
2013-08-07 23:58:30 +01:00
parent d0cbf417cf
commit cc684e02c3
28 changed files with 2881 additions and 139 deletions
+1484 -66
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,56 @@
INSERT INTO `m_permission`
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
VALUES
('transaction_savings', 'READ_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'READ', 0),
('transaction_savings', 'CREATE_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'CREATE', 1),
('transaction_savings', 'CREATE_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'CREATE', 0);
DROP TABLE IF EXISTS `m_savings_account_transfer`;
CREATE TABLE `m_savings_account_transfer` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`from_office_id` bigint(20) NOT NULL,
`to_office_id` bigint(20) NOT NULL,
`from_client_id` bigint(20) NOT NULL,
`to_client_id` bigint(20) NOT NULL,
`from_savings_account_id` bigint(20) DEFAULT NULL,
`to_savings_account_id` bigint(20) DEFAULT NULL,
`from_loan_account_id` bigint(20) DEFAULT NULL,
`to_loan_account_id` bigint(20) DEFAULT NULL,
`from_savings_transaction_id` bigint(20) DEFAULT NULL,
`from_loan_transaction_id` bigint(20) DEFAULT NULL,
`to_savings_transaction_id` bigint(20) DEFAULT NULL,
`to_loan_transaction_id` bigint(20) DEFAULT NULL,
`is_reversed` tinyint(1) NOT NULL,
`transaction_date` date NOT NULL,
`currency_code` varchar(3) NOT NULL,
`currency_digits` smallint(5) NOT NULL,
`amount` decimal(19,6) NOT NULL,
`description` varchar(200) NOT NULL,
PRIMARY KEY (`id`),
KEY `FKTRAN000000001` (`from_office_id`),
KEY `FKTRAN000000002` (`from_client_id`),
KEY `FKTRAN000000003` (`from_savings_account_id`),
KEY `FKTRAN000000004` (`to_office_id`),
KEY `FKTRAN000000005` (`to_client_id`),
KEY `FKTRAN000000006` (`to_savings_account_id`),
KEY `FKTRAN000000007` (`to_loan_account_id`),
KEY `FKTRAN000000008` (`from_savings_transaction_id`),
KEY `FKTRAN000000009` (`to_savings_transaction_id`),
KEY `FKTRAN000000010` (`to_loan_transaction_id`),
KEY `FKTRAN000000011` (`from_loan_account_id`),
KEY `FKTRAN000000012` (`from_loan_transaction_id`),
CONSTRAINT `FKTRAN000000001` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
CONSTRAINT `FKTRAN000000002` FOREIGN KEY (`from_client_id`) REFERENCES `m_client` (`id`),
CONSTRAINT `FKTRAN000000003` FOREIGN KEY (`from_savings_account_id`) REFERENCES `m_savings_account` (`id`),
CONSTRAINT `FKTRAN000000004` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`),
CONSTRAINT `FKTRAN000000005` FOREIGN KEY (`to_client_id`) REFERENCES `m_client` (`id`),
CONSTRAINT `FKTRAN000000006` FOREIGN KEY (`to_savings_account_id`) REFERENCES `m_savings_account` (`id`),
CONSTRAINT `FKTRAN000000007` FOREIGN KEY (`to_loan_account_id`) REFERENCES `m_loan` (`id`),
CONSTRAINT `FKTRAN000000008` FOREIGN KEY (`from_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
CONSTRAINT `FKTRAN000000009` FOREIGN KEY (`to_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
CONSTRAINT `FKTRAN000000010` FOREIGN KEY (`to_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
CONSTRAINT `FKTRAN000000011` FOREIGN KEY (`from_loan_account_id`) REFERENCES `m_loan` (`id`),
CONSTRAINT `FKTRAN000000012` FOREIGN KEY (`from_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@@ -563,4 +563,8 @@ public class CommandWrapper {
public boolean isSchedulerResource() {
return this.entityName.equalsIgnoreCase("SCHEDULER");
}
public boolean isAccountTransferResource() {
return this.entityName.equalsIgnoreCase("ACCOUNTTRANSFER");
}
}
@@ -808,6 +808,14 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder createAccountTransfer() {
this.actionName = "CREATE";
this.entityName = "ACCOUNTTRANSFER";
this.entityId = null;
this.href = "/accounttransfers";
return this;
}
public CommandWrapperBuilder savingsAccountDeposit(final Long accountId) {
this.actionName = "DEPOSIT";
this.entityName = "SAVINGSACCOUNT";
@@ -105,7 +105,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) {
NewCommandSourceHandler handler = null;
if (wrapper.isConfigurationResource()) {
if (wrapper.isAccountTransferResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createAccountTransferCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
}
else if (wrapper.isConfigurationResource()) {
handler = this.applicationContext.getBean("updateGlobalConfigurationCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDatatableResource()) {
if (wrapper.isCreateDatatable()) {
@@ -4,7 +4,7 @@ public enum JobName {
UPDATE_LOAN_SUMMARY("Update loan Summary"), UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), UPDATE_LOAN_PAID_IN_ADVANCE(
"Update Loan Paid In Advance"), APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS(
"Apply Holidays To Loans"),POST_INTEREST_FOR_SAVINGS("Post Interest For Savings");
"Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS("Post Interest For Savings");
private final String name;
@@ -0,0 +1,50 @@
/**
* 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.accounttransfers;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.mifosplatform.portfolio.accounttransfers.data.AccountTransferData;
public class AccountTransfersApiConstants {
public static final String ACCOUNT_TRANSFER_RESOURCE_NAME = "accounttransfer";
// general
public static final String localeParamName = "locale";
public static final String dateFormatParamName = "dateFormat";
// savings product and account parameters
public static final String idParamName = "id";
public static final String fromOfficeIdParamName = "fromOfficeId";
public static final String fromClientIdParamName = "fromClientId";
public static final String fromAccountIdParamName = "fromAccountId";
public static final String fromAccountTypeParamName = "fromAccountType";
public static final String toOfficeIdParamName = "toOfficeId";
public static final String toClientIdParamName = "toClientId";
public static final String toAccountIdParamName = "toAccountId";
public static final String toAccountTypeParamName = "toAccountType";
// transaction parameters
public static final String transferDateParamName = "transferDate";
public static final String transferAmountParamName = "transferAmount";
public static final String transferDescriptionParamName = "transferDescription";
public static final Set<String> REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName, dateFormatParamName,
fromOfficeIdParamName, fromClientIdParamName, fromAccountTypeParamName, fromAccountIdParamName, toOfficeIdParamName,
toClientIdParamName, toAccountTypeParamName, toAccountIdParamName, transferDateParamName, transferAmountParamName,
transferDescriptionParamName));
/**
* These parameters will match the class level parameters of
* {@link AccountTransferData}. Where possible, we try to get response
* parameters to match those of request parameters.
*/
public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName, transferDescriptionParamName,
"currency"));
}
@@ -0,0 +1,122 @@
/**
* 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.accounttransfers.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants;
import org.mifosplatform.portfolio.accounttransfers.data.AccountTransferData;
import org.mifosplatform.portfolio.accounttransfers.service.AccountTransfersReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/accounttransfers")
@Component
@Scope("singleton")
public class AccountTransfersApiResource {
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<AccountTransferData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
@Autowired
public AccountTransfersApiResource(final PlatformSecurityContext context,
final DefaultToApiJsonSerializer<AccountTransferData> toApiJsonSerializer,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final ApiRequestParameterHelper apiRequestParameterHelper,
final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
this.context = context;
this.toApiJsonSerializer = toApiJsonSerializer;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
}
@GET
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String template(@QueryParam("fromOfficeId") final Long fromOfficeId, @QueryParam("fromClientId") final Long fromClientId,
@QueryParam("fromAccountId") final Long fromAccountId, @QueryParam("fromAccountType") final Integer fromAccountType,
@QueryParam("toOfficeId") final Long toOfficeId, @QueryParam("toClientId") final Long toClientId,
@QueryParam("toAccountId") final Long toAccountId, @QueryParam("toAccountType") final Integer toAccountType,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
AccountTransferData transferData = this.accountTransfersReadPlatformService.retrieveTemplate(fromOfficeId, fromClientId,
fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transferData, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String create(final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createAccountTransfer().withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
@QueryParam("externalId") final String externalId, @QueryParam("offset") final Integer offset,
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
@QueryParam("sortOrder") final String sortOrder) {
context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forLoans(sqlSearch, externalId, offset, limit, orderBy, sortOrder);
final Page<AccountTransferData> transfers = this.accountTransfersReadPlatformService.retrieveAll(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transfers, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
}
@GET
@Path("{transferId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@PathParam("transferId") final Long transferId, @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
AccountTransferData transfer = this.accountTransfersReadPlatformService.retrieveOne(transferId);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transfer, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
}
}
@@ -0,0 +1,108 @@
/**
* 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.accounttransfers.data;
import java.math.BigDecimal;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
/**
* Immutable data object representing a savings account.
*/
@SuppressWarnings("unused")
public class AccountTransferData {
private final Long id;
private final Boolean reversed;
private final CurrencyData currency;
private final BigDecimal transferAmount;
private final LocalDate transferDate;
private final String transferDescription;
private final OfficeData fromOffice;
private final ClientData fromClient;
private final SavingsAccountData fromAccount;
private final OfficeData toOffice;
private final ClientData toClient;
private final SavingsAccountData toAccount;
// template
private final Collection<OfficeData> fromOfficeOptions;
private final Collection<ClientData> fromClientOptions;
private final Collection<SavingsAccountData> fromAccountOptions;
private final Collection<OfficeData> toOfficeOptions;
private final Collection<ClientData> toClientOptions;
private final Collection<SavingsAccountData> toAccountOptions;
public static AccountTransferData template(final OfficeData fromOffice, final ClientData fromClient,
final SavingsAccountData fromAccount, final LocalDate transferDate, final OfficeData toOffice, final ClientData toClient,
final SavingsAccountData toAccount, final Collection<OfficeData> fromOfficeOptions,
final Collection<ClientData> fromClientOptions, final Collection<SavingsAccountData> fromAccountOptions,
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
final Collection<SavingsAccountData> toAccountOptions) {
final Long id = null;
CurrencyData currency = null;
if (fromAccount != null) {
currency = fromAccount.currency();
}
final BigDecimal transferAmount = BigDecimal.ZERO;
final String transferDescription = null;
final Boolean reversed = null;
return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccount, currency, transferAmount, transferDate,
transferDescription, toOffice, toClient, toAccount, fromOfficeOptions, fromClientOptions, fromAccountOptions,
toOfficeOptions, toClientOptions, toAccountOptions);
}
public static AccountTransferData instance(final Long id, final Boolean reversed, final LocalDate transferDate,
final CurrencyData currency, final BigDecimal transferAmount, final String transferDescription, final OfficeData fromOffice,
final OfficeData toOffice, final ClientData fromClient, final ClientData toClient, final SavingsAccountData fromSavingsAccount,
final SavingsAccountData toSavingsAccount) {
return new AccountTransferData(id, reversed, fromOffice, fromClient, fromSavingsAccount, currency, transferAmount, transferDate,
transferDescription, toOffice, toClient, toSavingsAccount, null, null, null, null, null, null);
}
public static AccountTransferData transferBasicDetails(final Long id, final CurrencyData currency, final BigDecimal transferAmount,
final LocalDate transferDate, final String description, final Boolean reversed) {
return new AccountTransferData(id, reversed, null, null, null, currency, transferAmount, transferDate, description, null, null,
null, null, null, null, null, null, null);
}
private AccountTransferData(final Long id, final Boolean reversed, final OfficeData fromOffice, final ClientData fromClient,
final SavingsAccountData fromAccount, final CurrencyData currency, final BigDecimal transferAmount,
final LocalDate transferDate, final String transferDescription, final OfficeData toOffice, final ClientData toClient,
final SavingsAccountData toAccount, final Collection<OfficeData> fromOfficeOptions,
final Collection<ClientData> fromClientOptions, final Collection<SavingsAccountData> fromAccountOptions,
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
final Collection<SavingsAccountData> toAccountOptions) {
this.id = id;
this.reversed = reversed;
this.fromOffice = fromOffice;
this.fromClient = fromClient;
this.fromAccount = fromAccount;
this.toOffice = toOffice;
this.toClient = toClient;
this.toAccount = toAccount;
this.currency = currency;
this.transferAmount = transferAmount;
this.transferDate = transferDate;
this.transferDescription = transferDescription;
this.fromOfficeOptions = fromOfficeOptions;
this.fromClientOptions = fromClientOptions;
this.fromAccountOptions = fromAccountOptions;
this.toOfficeOptions = toOfficeOptions;
this.toClientOptions = toClientOptions;
this.toAccountOptions = toAccountOptions;
}
}
@@ -0,0 +1,108 @@
/**
* 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.accounttransfers.data;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.REQUEST_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.transferDescriptionParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.fromAccountIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.fromAccountTypeParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.fromClientIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.fromOfficeIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.toAccountIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.toAccountTypeParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.toClientIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.toOfficeIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.transferAmountParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.transferDateParamName;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
@Component
public class AccountTransfersDataValidator {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public AccountTransfersDataValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validate(final JsonCommand command) {
final String json = command.json();
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(ACCOUNT_TRANSFER_RESOURCE_NAME);
final JsonElement element = command.parsedJson();
final Long fromOfficeId = fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
baseDataValidator.reset().parameter(fromOfficeIdParamName).value(fromOfficeId).notNull().integerGreaterThanZero();
final Long fromClientId = fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
baseDataValidator.reset().parameter(fromClientIdParamName).value(fromClientId).notNull().integerGreaterThanZero();
final Long fromAccountId = fromApiJsonHelper.extractLongNamed(fromAccountIdParamName, element);
baseDataValidator.reset().parameter(fromAccountIdParamName).value(fromAccountId).notNull().integerGreaterThanZero();
final Integer fromAccountType = fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType)
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
final Long toOfficeId = fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
final Long toClientId = fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
baseDataValidator.reset().parameter(toClientIdParamName).value(toClientId).notNull().integerGreaterThanZero();
final Long toAccountId = fromApiJsonHelper.extractLongNamed(toAccountIdParamName, element);
baseDataValidator.reset().parameter(toAccountIdParamName).value(toAccountId).notNull().integerGreaterThanZero();
final Integer toAccountType = fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType)
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed(transferDateParamName, element);
baseDataValidator.reset().parameter(transferDateParamName).value(transactionDate).notNull();
final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transferAmountParamName, element);
baseDataValidator.reset().parameter(transferAmountParamName).value(transactionAmount).notNull().positiveAmount();
final String transactionDescription = fromApiJsonHelper.extractStringNamed(transferDescriptionParamName, element);
baseDataValidator.reset().parameter(transferDescriptionParamName).value(transactionDescription).notBlank().notExceedingLengthOf(200);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -0,0 +1,111 @@
/**
* 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.accounttransfers.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_savings_account_transfer")
public class AccountTransfer extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "from_office_id", nullable = false)
private Office fromOffice;
@ManyToOne
@JoinColumn(name = "from_client_id", nullable = false)
private Client fromClient;
@ManyToOne
@JoinColumn(name = "from_savings_account_id", nullable = true)
private SavingsAccount fromAccount;
@ManyToOne
@JoinColumn(name = "to_office_id", nullable = false)
private Office toOffice;
@ManyToOne
@JoinColumn(name = "to_client_id", nullable = false)
private Client toClient;
@ManyToOne
@JoinColumn(name = "to_savings_account_id", nullable = true)
private SavingsAccount toAccount;
@ManyToOne
@JoinColumn(name = "from_savings_transaction_id", nullable = true)
private SavingsAccountTransaction fromTransaction;
@ManyToOne
@JoinColumn(name = "to_savings_transaction_id", nullable = true)
private SavingsAccountTransaction toSavingsTransaction;
@Column(name = "is_reversed", nullable = false)
private final boolean reversed = false;
@Temporal(TemporalType.DATE)
@Column(name = "transaction_date")
private Date date;
@Embedded
private MonetaryCurrency currency;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@Column(name = "description", length = 100)
private String description;
public static AccountTransfer savingsToSavingsTransfer(final Office fromOffice, final Client fromClient,
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
final SavingsAccountTransaction withdrawal, final SavingsAccountTransaction deposit, final LocalDate transactionDate,
final Money transactionAmount, final String description) {
return new AccountTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toSavingsAccount, withdrawal, deposit,
transactionDate, transactionAmount, description);
}
protected AccountTransfer() {
//
}
private AccountTransfer(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount,
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
final SavingsAccountTransaction withdrawal, final SavingsAccountTransaction deposit, final LocalDate transactionDate,
final Money transactionAmount, final String description) {
this.fromOffice = fromOffice;
this.fromClient = fromClient;
this.fromAccount = fromSavingsAccount;
this.toOffice = toOffice;
this.toClient = toClient;
this.toAccount = toSavingsAccount;
this.fromTransaction = withdrawal;
this.toSavingsTransaction = deposit;
this.date = transactionDate.toDate();
this.currency = transactionAmount.getCurrency();
this.amount = transactionAmount.getAmountDefaultedToNullIfZero();
this.description = description;
}
}
@@ -0,0 +1,85 @@
/**
* 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.accounttransfers.domain;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.transferDescriptionParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.fromAccountIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.fromClientIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.fromOfficeIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.toAccountIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.toClientIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.toOfficeIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.transferAmountParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.transferDateParamName;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.gson.JsonElement;
@Service
public class AccountTransferAssembler {
private final ClientRepositoryWrapper clientRepository;
private final OfficeRepository officeRepository;
private final SavingsAccountAssembler savingsAccountAssembler;
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public AccountTransferAssembler(final ClientRepositoryWrapper clientRepository, final OfficeRepository officeRepository,
final SavingsAccountAssembler savingsAccountAssembler, final FromJsonHelper fromApiJsonHelper) {
this.clientRepository = clientRepository;
this.officeRepository = officeRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.fromApiJsonHelper = fromApiJsonHelper;
}
public AccountTransfer assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccountTransaction withdrawal,
final SavingsAccountTransaction deposit) {
final JsonElement element = command.parsedJson();
final Long fromOfficeId = fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
final Office fromOffice = this.officeRepository.findOne(fromOfficeId);
final Long fromClientId = fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId);
final Long toOfficeId = fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
final Office toOffice = this.officeRepository.findOne(toOfficeId);
final Long toClientId = fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId);
final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), transactionAmount);
final String description = command.stringValueOfParameterNamed(transferDescriptionParamName);
return AccountTransfer.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toSavingsAccount,
withdrawal, deposit, transactionDate, transactionMonetaryAmount, description);
}
}
@@ -0,0 +1,13 @@
/**
* 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.accounttransfers.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface AccountTransferRepository extends JpaRepository<AccountTransfer, Long>, JpaSpecificationExecutor<AccountTransfer> {
}
@@ -0,0 +1,15 @@
/**
* 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.accounttransfers.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class AccountTransferNotFoundException extends AbstractPlatformResourceNotFoundException {
public AccountTransferNotFoundException(final Long id) {
super("error.msg.accounttransfer.id.invalid", "Account transfer with identifier " + id + " does not exist", id);
}
}
@@ -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.accounttransfers.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.accounttransfers.service.AccountTransfersWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CreateAccountTransferCommandHandler implements NewCommandSourceHandler {
private final AccountTransfersWritePlatformService writePlatformService;
@Autowired
public CreateAccountTransferCommandHandler(final AccountTransfersWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return writePlatformService.create(command);
}
}
@@ -0,0 +1,20 @@
/**
* 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.accounttransfers.service;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.portfolio.accounttransfers.data.AccountTransferData;
import org.mifosplatform.portfolio.group.service.SearchParameters;
public interface AccountTransfersReadPlatformService {
AccountTransferData retrieveTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
Page<AccountTransferData> retrieveAll(SearchParameters searchParameters);
AccountTransferData retrieveOne(Long transferId);
}
@@ -0,0 +1,291 @@
/**
* 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.accounttransfers.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.exception.UnsupportedCommandException;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.core.service.PaginationHelper;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.organisation.office.service.OfficeReadPlatformService;
import org.mifosplatform.portfolio.accounttransfers.data.AccountTransferData;
import org.mifosplatform.portfolio.accounttransfers.exception.AccountTransferNotFoundException;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class AccountTransfersReadPlatformServiceImpl implements AccountTransfersReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final ClientReadPlatformService clientReadPlatformService;
private final OfficeReadPlatformService officeReadPlatformService;
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
// mapper
private final AccountTransfersMapper accountTransfersMapper;
// pagination
private final PaginationHelper<AccountTransferData> paginationHelper = new PaginationHelper<AccountTransferData>();
@Autowired
public AccountTransfersReadPlatformServiceImpl(final RoutingDataSource dataSource,
final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
final SavingsAccountReadPlatformService savingsAccountReadPlatformService) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.clientReadPlatformService = clientReadPlatformService;
this.officeReadPlatformService = officeReadPlatformService;
this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
this.accountTransfersMapper = new AccountTransfersMapper();
}
@Override
public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) {
Integer mostRelevantFromAccountType = fromAccountType;
if (mostRelevantFromAccountType == null) {
mostRelevantFromAccountType = 2; // savings
}
Integer mostRelevantToAccountType = toAccountType;
if (mostRelevantToAccountType == null) {
mostRelevantToAccountType = 2; // savings
}
// check for now that only savings account type is passed and used.
if (mostRelevantFromAccountType != 2) { throw new UnsupportedCommandException("transferFromLoanAccount"); }
// from settings
OfficeData fromOffice = null;
ClientData fromClient = null;
SavingsAccountData fromAccount = null;
OfficeData toOffice = null;
ClientData toClient = null;
SavingsAccountData toAccount = null;
// template
Collection<SavingsAccountData> fromAccountOptions = null;
Collection<SavingsAccountData> toAccountOptions = null;
Long mostRelevantFromOfficeId = fromOfficeId;
Long mostRelevantFromClientId = fromClientId;
Long mostRelevantToOfficeId = toOfficeId;
Long mostRelevantToClientId = toClientId;
if (fromAccountId != null && fromAccountType == 2) {
fromAccount = this.savingsAccountReadPlatformService.retrieveOne(fromAccountId);
// override provided fromClient with client of account
mostRelevantFromClientId = fromAccount.clientId();
}
if (mostRelevantFromClientId != null) {
fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
mostRelevantFromOfficeId = fromClient.officeId();
toClient = fromClient;
fromAccountOptions = this.savingsAccountReadPlatformService.retrieveAllForLookup(mostRelevantFromClientId);
toAccountOptions = new ArrayList<SavingsAccountData>(fromAccountOptions);
toAccountOptions.remove(fromAccount);
}
Collection<OfficeData> fromOfficeOptions = null;
Collection<ClientData> fromClientOptions = null;
if (mostRelevantFromOfficeId != null) {
fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
}
// defaults
LocalDate transferDate = DateUtils.getLocalDateOfTenant();
toOffice = fromOffice;
Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
Collection<ClientData> toClientOptions = fromClientOptions;
if (toAccountId != null) {
toAccount = this.savingsAccountReadPlatformService.retrieveOne(toAccountId);
// override provided toClient with client of account
mostRelevantToClientId = toAccount.clientId();
}
if (mostRelevantToClientId != null && mostRelevantToClientId != mostRelevantFromClientId) {
toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
mostRelevantToOfficeId = toClient.officeId();
toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
toAccountOptions = this.savingsAccountReadPlatformService.retrieveAllForLookup(mostRelevantToClientId);
toAccountOptions.remove(fromAccount);
}
if (mostRelevantToOfficeId != null && mostRelevantToOfficeId != mostRelevantFromOfficeId) {
toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
if (toClientOptions != null && toClientOptions.size() == 1) {
toClient = new ArrayList<ClientData>(toClientOptions).get(0);
toAccountOptions = this.savingsAccountReadPlatformService.retrieveAllForLookup(mostRelevantToClientId);
toAccountOptions.remove(fromAccount);
}
}
return AccountTransferData.template(fromOffice, fromClient, fromAccount, transferDate, toOffice, toClient, toAccount,
fromOfficeOptions, fromClientOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountOptions);
}
@Override
public Page<AccountTransferData> retrieveAll(final SearchParameters searchParameters) {
StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
sqlBuilder.append(accountTransfersMapper.schema());
if (searchParameters.isOrderByRequested()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
sqlBuilder.append(" limit ").append(searchParameters.getLimit());
if (searchParameters.isOffset()) {
sqlBuilder.append(" offset ").append(searchParameters.getOffset());
}
}
final Object[] objectArray = new Object[2];
int arrayPos = 0;
final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
final String sqlCountRows = "SELECT FOUND_ROWS()";
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
this.accountTransfersMapper);
}
@Override
public AccountTransferData retrieveOne(final Long transferId) {
try {
final String sql = "select " + this.accountTransfersMapper.schema() + " where sat.id = ?";
return this.jdbcTemplate.queryForObject(sql, this.accountTransfersMapper, new Object[] { transferId });
} catch (EmptyResultDataAccessException e) {
throw new AccountTransferNotFoundException(transferId);
}
}
private static final class AccountTransfersMapper implements RowMapper<AccountTransferData> {
private final String schemaSql;
public AccountTransfersMapper() {
final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder.append("sat.id as id, sat.is_reversed as isReversed,");
sqlBuilder.append("sat.transaction_date as transferDate, sat.amount as transferAmount,");
sqlBuilder.append("sat.description as transferDescription,");
sqlBuilder.append("sat.currency_code as currencyCode, sat.currency_digits as currencyDigits,");
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,");
sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,");
sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,");
sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,");
sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,");
sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
sqlBuilder.append("fromsavtran.id as fromSavingsAccountTransactionId,");
sqlBuilder.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,");
sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,");
sqlBuilder.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType");
sqlBuilder.append(" FROM m_savings_account_transfer sat ");
sqlBuilder.append("join m_currency curr on curr.code = sat.currency_code ");
sqlBuilder.append("join m_office fromoff on fromoff.id = sat.from_office_id ");
sqlBuilder.append("join m_office tooff on tooff.id = sat.to_office_id ");
sqlBuilder.append("join m_client fromclient on fromclient.id = sat.from_client_id ");
sqlBuilder.append("join m_client toclient on toclient.id = sat.to_client_id ");
sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = sat.from_savings_account_id ");
sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = sat.to_savings_account_id ");
sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = sat.from_savings_transaction_id ");
sqlBuilder.append("left join m_savings_account_transaction tosavtran on tosavtran.id = sat.to_savings_transaction_id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public AccountTransferData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final boolean reversed = rs.getBoolean("isReversed");
final LocalDate transferDate = JdbcSupport.getLocalDate(rs, "transferDate");
final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transferAmount");
final String transferDescription = rs.getString("transferDescription");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol,
currencyNameCode);
final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
final String fromOfficeName = rs.getString("fromOfficeName");
OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null);
final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
final String toOfficeName = rs.getString("toOfficeName");
OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null);
final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId");
final String fromClientName = rs.getString("fromClientName");
ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName);
final Long toClientId = JdbcSupport.getLong(rs, "toClientId");
final String toClientName = rs.getString("toClientName");
ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName);
final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId");
final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo");
SavingsAccountData fromSavingsAccount = SavingsAccountData.lookup(fromSavingsAccountId, fromSavingsAccountNo);
final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId");
final String toSavingsAccountNo = rs.getString("toSavingsAccountNo");
SavingsAccountData toSavingsAccount = SavingsAccountData.lookup(toSavingsAccountId, toSavingsAccountNo);
return AccountTransferData.instance(id, reversed, transferDate, currency, transferAmount, transferDescription, fromOffice,
toOffice, fromClient, toClient, fromSavingsAccount, toSavingsAccount);
}
}
}
@@ -0,0 +1,14 @@
/**
* 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.accounttransfers.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface AccountTransfersWritePlatformService {
CommandProcessingResult create(JsonCommand command);
}
@@ -0,0 +1,91 @@
/**
* 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.accounttransfers.service;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.fromAccountIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.toAccountIdParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.transferAmountParamName;
import static org.mifosplatform.portfolio.accounttransfers.AccountTransfersApiConstants.transferDateParamName;
import java.math.BigDecimal;
import java.util.Locale;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.portfolio.accounttransfers.data.AccountTransfersDataValidator;
import org.mifosplatform.portfolio.accounttransfers.domain.AccountTransfer;
import org.mifosplatform.portfolio.accounttransfers.domain.AccountTransferAssembler;
import org.mifosplatform.portfolio.accounttransfers.domain.AccountTransferRepository;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountDomainService;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountTransfersWritePlatformServiceImpl implements AccountTransfersWritePlatformService {
private final AccountTransfersDataValidator accountTransfersDataValidator;
private final AccountTransferAssembler accountTransferAssembler;
private final AccountTransferRepository accountTransferRepository;
private final SavingsAccountAssembler savingsAccountAssembler;
private final SavingsAccountDomainService savingsAccountDomainService;
@Autowired
public AccountTransfersWritePlatformServiceImpl(final AccountTransfersDataValidator accountTransfersDataValidator,
final AccountTransferAssembler accountTransferAssembler, final AccountTransferRepository accountTransferRepository,
final SavingsAccountAssembler savingsAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService) {
this.accountTransfersDataValidator = accountTransfersDataValidator;
this.accountTransferAssembler = accountTransferAssembler;
this.accountTransferRepository = accountTransferRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.savingsAccountDomainService = savingsAccountDomainService;
}
@Transactional
@Override
public CommandProcessingResult create(final JsonCommand command) {
this.accountTransfersDataValidator.validate(command);
final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
// check fromAccountType settings to see if its a savings account
// withdrawal or a loan account transfer
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId);
final PaymentDetail paymentDetail = null;
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
transactionDate, transactionAmount, paymentDetail);
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId);
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate,
transactionAmount, paymentDetail);
AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToSavingsTransfer(command, withdrawal, deposit);
this.accountTransferRepository.saveAndFlush(transferTransaction);
return new CommandProcessingResultBuilder() //
.withSavingsId(fromSavingsId) //
.withEntityId(transferTransaction.getId()) //
.build();
}
}
@@ -23,7 +23,6 @@ import org.mifosplatform.portfolio.group.data.GroupGeneralData;
*/
final public class ClientData implements Comparable<ClientData> {
private final Long id;
private final String accountNo;
private final String externalId;
@@ -49,16 +48,17 @@ final public class ClientData implements Comparable<ClientData> {
// associations
private final Collection<GroupGeneralData> groups;
// template
private final Collection<OfficeData> officeOptions;
private final Collection<StaffData> staffOptions;
@SuppressWarnings("unused")
private final Collection<CodeValueData> closureReasons;
public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection<OfficeData> officeOptions,
public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection<OfficeData> officeOptions,
final Collection<StaffData> staffOptions, final Collection<CodeValueData> closureReasons) {
return new ClientData(null, null, officeId, null, null, null, null, null, null, null, null, joinedDate, null, null, null, officeOptions, null, staffOptions, closureReasons);
return new ClientData(null, null, officeId, null, null, null, null, null, null, null, null, joinedDate, null, null, null,
officeOptions, null, staffOptions, closureReasons);
}
public static ClientData templateOnTop(final ClientData clientData, final ClientData templateData) {
@@ -81,11 +81,12 @@ final public class ClientData implements Comparable<ClientData> {
final String officeName) {
return new ClientData(accountNo, status, officeId, officeName, id, firstname, middlename, lastname, fullname, displayName, null,
null, null, null, null, null, null,null, null);
null, null, null, null, null, null, null, null);
}
public static ClientData lookup(final Long id, final String displayName, final Long officeId, final String officeName) {
return new ClientData(null, null, officeId, officeName, id, null, null, null, null, displayName, null, null, null, null, null, null, null, null, null);
return new ClientData(null, null, officeId, officeName, id, null, null, null, null, displayName, null, null, null, null, null,
null, null, null, null);
}
public static ClientData instance(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName,
@@ -99,8 +100,8 @@ final public class ClientData implements Comparable<ClientData> {
private ClientData(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName, final Long id,
final String firstname, final String middlename, final String lastname, final String fullname, final String displayName,
final String externalId, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
final Collection<OfficeData> allowedOffices, final Collection<GroupGeneralData> groups, final Collection<StaffData> staffOptions,
final Collection<CodeValueData> closureReasons) {
final Collection<OfficeData> allowedOffices, final Collection<GroupGeneralData> groups,
final Collection<StaffData> staffOptions, final Collection<CodeValueData> closureReasons) {
this.accountNo = accountNo;
this.status = status;
if (status != null) {
@@ -122,7 +123,7 @@ final public class ClientData implements Comparable<ClientData> {
if (imageId != null) {
this.imagePresent = Boolean.TRUE;
} else {
this.imagePresent = Boolean.FALSE;
this.imagePresent = null;
}
this.staffId = staffId;
this.staffName = staffName;
@@ -189,7 +190,6 @@ final public class ClientData implements Comparable<ClientData> {
.toHashCode();
}
// TODO - kw - look into removing usage of the getters below
public String getExternalId() {
return this.externalId;
}
@@ -205,5 +205,4 @@ final public class ClientData implements Comparable<ClientData> {
public LocalDate getActivationDate() {
return this.activationDate;
}
}
@@ -8,6 +8,8 @@ package org.mifosplatform.portfolio.savings.data;
import java.math.BigDecimal;
import java.util.Collection;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
@@ -93,6 +95,26 @@ public class SavingsAccountData {
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions);
}
public static SavingsAccountData lookup(final Long accountId, final String accountNo) {
final Collection<SavingsProductData> productOptions = null;
final Collection<StaffData> fieldOfficerOptions = null;
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
final Collection<SavingsAccountTransactionData> transactions = null;
return new SavingsAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, transactions, productOptions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions);
}
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template,
final Collection<SavingsAccountTransactionData> transactions) {
@@ -263,4 +285,23 @@ public class SavingsAccountData {
public Long productId() {
return this.savingsProductId;
}
public CurrencyData currency() {
return this.currency;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (obj.getClass() != getClass()) { return false; }
SavingsAccountData rhs = (SavingsAccountData) obj;
return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).toHashCode();
}
}
@@ -11,6 +11,7 @@ import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.accounttransfers.data.AccountTransferData;
import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData;
/**
@@ -29,16 +30,18 @@ public class SavingsAccountTransactionData {
private final BigDecimal amount;
private final BigDecimal runningBalance;
private final boolean reversed;
private final AccountTransferData transfer;
// templates
final Collection<CodeValueData> paymentTypeOptions;
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed) {
final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed,
final AccountTransferData transfer) {
final Collection<CodeValueData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
amount, runningBalance, reversed, paymentTypeOptions);
amount, runningBalance, reversed, transfer, paymentTypeOptions);
}
public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo,
@@ -51,7 +54,7 @@ public class SavingsAccountTransactionData {
final PaymentDetailData paymentDetailData = null;
final Collection<CodeValueData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate,
currency, amount, runningBalance, reversed, null);
currency, amount, runningBalance, reversed, null, null);
}
public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData,
@@ -60,13 +63,13 @@ public class SavingsAccountTransactionData {
savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId,
savingsAccountTransactionData.accountNo, savingsAccountTransactionData.date, savingsAccountTransactionData.currency,
savingsAccountTransactionData.amount, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed,
paymentTypeOptions);
savingsAccountTransactionData.transfer, 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 BigDecimal runningBalance, final boolean reversed,
final Collection<CodeValueData> paymentTypeOptions) {
final AccountTransferData transfer, final Collection<CodeValueData> paymentTypeOptions) {
this.id = id;
this.transactionType = transactionType;
this.paymentDetailData = paymentDetailData;
@@ -77,6 +80,7 @@ public class SavingsAccountTransactionData {
this.amount = amount;
this.runningBalance = runningBalance;
this.reversed = reversed;
this.transfer = transfer;
this.paymentTypeOptions = paymentTypeOptions;
}
}
@@ -699,7 +699,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
boolean transactionBeforeLastInterestPosting = false;
for (SavingsAccountTransaction transaction : retreiveListOfTransactions()) {
if (transaction.isNotReversed() && transaction.isInterestPostingAndNotReversed() && transaction.isAfter(transactionDate)) {
if (transaction.isInterestPostingAndNotReversed() && transaction.isAfter(transactionDate)) {
transactionBeforeLastInterestPosting = true;
break;
}
@@ -0,0 +1,21 @@
/**
* 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.domain;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
public interface SavingsAccountDomainService {
SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail);
SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail);
}
@@ -0,0 +1,117 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService {
private final SavingsAccountRepositoryWrapper savingsAccountRepository;
private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
@Autowired
public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository,
final SavingsAccountTransactionRepository savingsAccountTransactionRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
final JournalEntryWritePlatformService journalEntryWritePlatformService) {
this.savingsAccountRepository = savingsAccountRepository;
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
}
@Transactional
@Override
public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccountTransaction withdrawal = account.withdraw(fmt, transactionDate, transactionAmount, existingTransactionIds,
existingReversedTransactionIds, paymentDetail);
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
saveTransactionToGenerateTransactionId(withdrawal);
this.savingsAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
return withdrawal;
}
@Transactional
@Override
public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccountTransaction deposit = account.deposit(fmt, transactionDate, transactionAmount, existingTransactionIds,
existingReversedTransactionIds, paymentDetail);
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
saveTransactionToGenerateTransactionId(deposit);
this.savingsAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
return deposit;
}
private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
this.savingsAccountTransactionRepository.save(transaction);
return transaction.getId();
}
private void postJournalEntries(final SavingsAccount savingsAccount, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
}
}
@@ -17,6 +17,8 @@ public interface SavingsAccountReadPlatformService {
Page<SavingsAccountData> retrieveAll(SearchParameters searchParameters);
Collection<SavingsAccountData> retrieveAllForLookup(Long clientId);
SavingsAccountData retrieveOne(Long savingsId);
SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long productId, boolean staffInSelectedOfficeOnly);
@@ -26,6 +26,7 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.organisation.staff.service.StaffReadPlatformService;
import org.mifosplatform.portfolio.accounttransfers.data.AccountTransferData;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
@@ -74,7 +75,6 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
// pagination
private final PaginationHelper<SavingsAccountData> paginationHelper = new PaginationHelper<SavingsAccountData>();
@Autowired
public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
@@ -93,6 +93,16 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
this.annualFeeMapper = new SavingsAccountAnnualFeeMapper();
}
@Override
public Collection<SavingsAccountData> retrieveAllForLookup(final Long clientId) {
StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema());
sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300");
Object[] queryParameters = new Object[] { clientId };
return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters);
}
@Override
public Page<SavingsAccountData> retrieveAll(final SearchParameters searchParameters) {
@@ -148,12 +158,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
public SavingsAccountData retrieveOne(final Long accountId) {
try {
this.context.authenticatedUser();
final String sql = "select " + this.savingAccountMapper.schema() + " where sa.id = ?";
final SavingAccountMapper mapper = new SavingAccountMapper();
final String sql = "select " + mapper.schema() + " where sa.id = ?";
return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { accountId });
return this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, new Object[] { accountId });
} catch (EmptyResultDataAccessException e) {
throw new SavingsAccountNotFoundException(accountId);
}
@@ -518,6 +525,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,");
sqlBuilder.append("fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,");
sqlBuilder.append("fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,");
sqlBuilder.append("fromtran.description as fromTransferDescription,");
sqlBuilder.append("totran.id as toTransferId, totran.is_reversed as toTransferReversed,");
sqlBuilder.append("totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,");
sqlBuilder.append("totran.description as toTransferDescription,");
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, ");
@@ -528,6 +541,8 @@ 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_savings_account_transfer fromtran on fromtran.from_savings_transaction_id = tr.id ");
sqlBuilder.append("left join m_savings_account_transfer totran on totran.to_savings_transaction_id = tr.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 ");
@@ -576,8 +591,29 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol,
currencyNameCode);
AccountTransferData transfer = null;
final Long fromTransferId = JdbcSupport.getLong(rs, "fromTransferId");
final Long toTransferId = JdbcSupport.getLong(rs, "toTransferId");
if (fromTransferId != null) {
final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, "fromTransferDate");
final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fromTransferAmount");
final boolean fromTransferReversed = rs.getBoolean("fromTransferReversed");
final String fromTransferDescription = rs.getString("fromTransferDescription");
transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate,
fromTransferDescription, fromTransferReversed);
} else if (toTransferId != null) {
final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, "toTransferDate");
final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "toTransferAmount");
final boolean toTransferReversed = rs.getBoolean("toTransferReversed");
final String toTransferDescription = rs.getString("toTransferDescription");
transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate,
toTransferDescription, toTransferReversed);
}
return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency,
amount, runningBalance, reversed);
amount, runningBalance, reversed, transfer);
}
}
@@ -33,6 +33,7 @@ import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatf
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDataValidator;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountDomainService;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
@@ -53,6 +54,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final SavingsAccountDomainService savingsAccountDomainService;
@Autowired
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -62,7 +64,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator,
final PaymentDetailWritePlatformService paymentDetailWritePlatformService,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
final JournalEntryWritePlatformService journalEntryWritePlatformService) {
final JournalEntryWritePlatformService journalEntryWritePlatformService,
final SavingsAccountDomainService savingsAccountDomainService) {
this.context = context;
this.savingAccountRepository = savingAccountRepository;
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
@@ -71,6 +74,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.savingsAccountDomainService = savingsAccountDomainService;
}
@Transactional
@@ -120,36 +124,19 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN);
final SavingsAccountTransaction deposit = account.deposit(fmt, transactionDate, transactionAmount, existingTransactionIds,
existingReversedTransactionIds, paymentDetail);
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
final Long transactionId = saveTransactionToGenerateTransactionId(deposit);
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate,
transactionAmount, paymentDetail);
return new CommandProcessingResultBuilder() //
.withEntityId(transactionId) //
.withEntityId(deposit.getId()) //
.withOfficeId(account.officeId()) //
.withClientId(account.clientId()) //
.withGroupId(account.groupId()) //
.withSavingsId(savingsId) //
.with(changes)//
.with(changes) //
.build();
}
@@ -162,42 +149,24 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Override
public CommandProcessingResult withdrawal(final Long savingsId, final JsonCommand command) {
this.context.authenticatedUser();
this.savingsAccountTransactionDataValidator.validate(command);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN);
final SavingsAccountTransaction withdrawal = account.withdraw(fmt, transactionDate, transactionAmount, existingTransactionIds,
existingReversedTransactionIds, paymentDetail);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal);
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate,
transactionAmount, paymentDetail);
return new CommandProcessingResultBuilder() //
.withEntityId(transactionId) //
.withEntityId(withdrawal.getId()) //
.withOfficeId(account.officeId()) //
.withClientId(account.clientId()) //
.withGroupId(account.groupId()) //
@@ -273,7 +242,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.build();
}
@Transactional
private void postInterest(final SavingsAccount account) {
final List<Long> existingTransactionIds = new ArrayList<Long>();
@@ -290,7 +258,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@CronTarget(jobName = JobName.POST_INTEREST_FOR_SAVINGS)
@Override
public void postInterestForAccounts() {
final List<SavingsAccount> savingsAccounts = this.savingAccountRepository.findSavingAccountByStatus(SavingsAccountStatusType.ACTIVE.getValue());
final List<SavingsAccount> savingsAccounts = this.savingAccountRepository.findSavingAccountByStatus(SavingsAccountStatusType.ACTIVE
.getValue());
for (final SavingsAccount savingsAccount : savingsAccounts) {
this.savingAccountAssembler.assignSavingAccountHelpers(savingsAccount);
postInterest(savingsAccount);