Merge branch 'openingbalances' of git://github.com/binnygopinath/mifosx into binnygopinath-openingbalances

Conflicts:
	mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
This commit is contained in:
Vishwas Babu A J
2015-01-08 06:32:58 -08:00
18 changed files with 500 additions and 8 deletions
@@ -173,4 +173,13 @@ public class GLAccountData {
return this.glCode; return this.glCode;
} }
public EnumOptionData getType() {
return this.type;
}
public Integer getTypeId() {
if (this.type != null) { return this.type.getId().intValue(); }
return null;
}
} }
@@ -24,6 +24,7 @@ import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.mifosplatform.accounting.journalentry.data.JournalEntryAssociationParametersData; import org.mifosplatform.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.mifosplatform.accounting.journalentry.data.JournalEntryData; import org.mifosplatform.accounting.journalentry.data.JournalEntryData;
import org.mifosplatform.accounting.journalentry.data.OfficeOpeningBalancesData;
import org.mifosplatform.accounting.journalentry.service.JournalEntryReadPlatformService; import org.mifosplatform.accounting.journalentry.service.JournalEntryReadPlatformService;
import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.CommandWrapperBuilder;
@@ -53,7 +54,7 @@ public class JournalEntriesApiResource {
private final String resourceNameForPermission = "JOURNALENTRY"; private final String resourceNameForPermission = "JOURNALENTRY";
private final JournalEntryReadPlatformService journalEntryReadPlatformService; private final JournalEntryReadPlatformService journalEntryReadPlatformService;
private final DefaultToApiJsonSerializer<JournalEntryData> apiJsonSerializerService; private final DefaultToApiJsonSerializer<Object> apiJsonSerializerService;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@@ -61,7 +62,7 @@ public class JournalEntriesApiResource {
@Autowired @Autowired
public JournalEntriesApiResource(final PlatformSecurityContext context, public JournalEntriesApiResource(final PlatformSecurityContext context,
final JournalEntryReadPlatformService journalEntryReadPlatformService, final JournalEntryReadPlatformService journalEntryReadPlatformService,
final DefaultToApiJsonSerializer<JournalEntryData> toApiJsonSerializer, final DefaultToApiJsonSerializer<Object> toApiJsonSerializer,
final ApiRequestParameterHelper apiRequestParameterHelper, final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.context = context; this.context = context;
@@ -131,6 +132,10 @@ public class JournalEntriesApiResource {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRunningBalanceForJournalEntry() final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRunningBalanceForJournalEntry()
.withJson(jsonRequestBody).build(); .withJson(jsonRequestBody).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "defineOpeningBalance")) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().defineOpeningBalanceForJournalEntry()
.withJson(jsonRequestBody).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else { } else {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createJournalEntry().withJson(jsonRequestBody).build(); final CommandWrapper commandRequest = new CommandWrapperBuilder().createJournalEntry().withJson(jsonRequestBody).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
@@ -156,6 +161,18 @@ public class JournalEntriesApiResource {
return this.apiJsonSerializerService.serialize(result); return this.apiJsonSerializerService.serialize(result);
} }
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Path("openingbalance")
public String retrieveOpeningBalance(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
final OfficeOpeningBalancesData officeOpeningBalancesData = this.journalEntryReadPlatformService.retrieveOfficeOpeningBalances(officeId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.apiJsonSerializerService.serialize(settings, officeOpeningBalancesData);
}
private boolean is(final String commandParam, final String commandValue) { private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
} }
@@ -8,6 +8,7 @@ package org.mifosplatform.accounting.journalentry.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.data.CurrencyData;
@@ -99,6 +100,39 @@ public class JournalEntryData {
this.currency = currency; this.currency = currency;
} }
public static JournalEntryData fromGLAccountData(final GLAccountData glAccountData) {
final Long id = null;
final Long officeId = null;
final String officeName = null;
final String glAccountName = glAccountData.getName();
final Long glAccountId = glAccountData.getId();
final String glAccountCode = glAccountData.getGlCode();
final EnumOptionData glAccountClassification = glAccountData.getType();
final LocalDate transactionDate = null;
final EnumOptionData entryType = null;
final BigDecimal amount = null;
final String transactionId = null;
final Boolean manualEntry = null;
final EnumOptionData entityType = null;
final Long entityId = null;
final Long createdByUserId = null;
final LocalDate createdDate = null;
final String createdByUserName = null;
final String comments = null;
final Boolean reversed = null;
final String referenceNumber = null;
final BigDecimal officeRunningBalance = null;
final BigDecimal organizationRunningBalance = null;
final Boolean runningBalanceComputed = null;
final TransactionDetailData transactionDetailData = null;
final CurrencyData currency = null;
return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glAccountCode, glAccountClassification,
transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, createdDate,
createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance,
runningBalanceComputed, transactionDetailData, currency);
}
public Long getId() { public Long getId() {
return this.id; return this.id;
} }
@@ -122,4 +156,8 @@ public class JournalEntryData {
public Long getOfficeId() { public Long getOfficeId() {
return this.officeId; return this.officeId;
} }
public String getTransactionId() {
return transactionId;
}
} }
@@ -0,0 +1,51 @@
package org.mifosplatform.accounting.journalentry.data;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
public class OfficeOpeningBalancesData {
@SuppressWarnings("unused")
private final Long officeId;
@SuppressWarnings("unused")
private final String officeName;
@SuppressWarnings("unused")
private final LocalDate transactionDate;
@SuppressWarnings("unused")
private final GLAccountData contraAccount;
@SuppressWarnings("unused")
private final List<JournalEntryData> assetAccountOpeningBalances;
@SuppressWarnings("unused")
private final List<JournalEntryData> liabityAccountOpeningBalances;
@SuppressWarnings("unused")
private final List<JournalEntryData> incomeAccountOpeningBalances;
@SuppressWarnings("unused")
private final List<JournalEntryData> equityAccountOpeningBalances;
@SuppressWarnings("unused")
private final List<JournalEntryData> expenseAccountOpeningBalances;
private OfficeOpeningBalancesData(final Long officeId, final String officeName, final LocalDate transactionDate,
final GLAccountData contraAccount, final List<JournalEntryData> assetAccountOpeningBalances,
final List<JournalEntryData> liabityAccountOpeningBalances, final List<JournalEntryData> incomeAccountOpeningBalances,
final List<JournalEntryData> equityAccountOpeningBalances, final List<JournalEntryData> expenseAccountOpeningBalances) {
this.officeId = officeId;
this.officeName = officeName;
this.transactionDate = transactionDate;
this.contraAccount = contraAccount;
this.assetAccountOpeningBalances = assetAccountOpeningBalances;
this.liabityAccountOpeningBalances = liabityAccountOpeningBalances;
this.incomeAccountOpeningBalances = incomeAccountOpeningBalances;
this.equityAccountOpeningBalances = equityAccountOpeningBalances;
this.expenseAccountOpeningBalances = expenseAccountOpeningBalances;
}
public static OfficeOpeningBalancesData createNew(final Long officeId, final String officeName, final LocalDate transactionDate,
final GLAccountData contraAccount, final List<JournalEntryData> assetAccountOpeningBalances,
final List<JournalEntryData> liabityAccountOpeningBalances, final List<JournalEntryData> incomeAccountOpeningBalances,
final List<JournalEntryData> equityAccountOpeningBalances, final List<JournalEntryData> expenseAccountOpeningBalances) {
return new OfficeOpeningBalancesData(officeId, officeName, transactionDate, contraAccount, assetAccountOpeningBalances,
liabityAccountOpeningBalances, incomeAccountOpeningBalances, equityAccountOpeningBalances, expenseAccountOpeningBalances);
}
}
@@ -177,4 +177,8 @@ public class JournalEntry extends AbstractAuditableCustom<AppUser, Long> {
return this.paymentDetail; return this.paymentDetail;
} }
public String getTransactionId() {
return transactionId;
}
} }
@@ -17,4 +17,10 @@ public interface JournalEntryRepository extends JpaRepository<JournalEntry, Long
@Query("from JournalEntry journalEntry where journalEntry.transactionId= :transactionId and journalEntry.reversed is false and journalEntry.manualEntry is true") @Query("from JournalEntry journalEntry where journalEntry.transactionId= :transactionId and journalEntry.reversed is false and journalEntry.manualEntry is true")
List<JournalEntry> findUnReversedManualJournalEntriesByTransactionId(@Param("transactionId") String transactionId); List<JournalEntry> findUnReversedManualJournalEntriesByTransactionId(@Param("transactionId") String transactionId);
@Query("select DISTINCT j.transactionId from JournalEntry j where j.transactionId not in (select DISTINCT je.transactionId from JournalEntry je where je.glAccount.id = :contraId)")
List<String> findNonContraTansactionIds(@Param("contraId") Long contraId);
@Query("select DISTINCT j.transactionId from JournalEntry j where j.office.id = :officeId and j.glAccount.id = :contraId and j.reversed is false and j.transactionId not in (select DISTINCT je.reversalJournalEntry.transactionId from JournalEntry je where je.reversed is true)")
List<String> findNonReversedContraTansactionIds(@Param("contraId") Long contraId, @Param("officeId") Long officeId);
} }
@@ -0,0 +1,25 @@
package org.mifosplatform.accounting.journalentry.handler;
import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DefineOpeningBalanceCommandHandler implements NewCommandSourceHandler {
private final JournalEntryWritePlatformService writePlatformService;
@Autowired
public DefineOpeningBalanceCommandHandler(final JournalEntryWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Override
public CommandProcessingResult processCommand(JsonCommand command) {
return this.writePlatformService.defineOpeningBalance(command);
}
}
@@ -9,6 +9,7 @@ import java.util.Date;
import org.mifosplatform.accounting.journalentry.data.JournalEntryAssociationParametersData; import org.mifosplatform.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.mifosplatform.accounting.journalentry.data.JournalEntryData; import org.mifosplatform.accounting.journalentry.data.JournalEntryData;
import org.mifosplatform.accounting.journalentry.data.OfficeOpeningBalancesData;
import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.core.service.SearchParameters; import org.mifosplatform.infrastructure.core.service.SearchParameters;
@@ -19,4 +20,6 @@ public interface JournalEntryReadPlatformService {
Page<JournalEntryData> retrieveAll(SearchParameters searchParameters, Long glAccountId, Boolean onlyManualEntries, Date fromDate, Page<JournalEntryData> retrieveAll(SearchParameters searchParameters, Long glAccountId, Boolean onlyManualEntries, Date fromDate,
Date toDate, String transactionId, Integer entityType, JournalEntryAssociationParametersData associationParametersData); Date toDate, String transactionId, Integer entityType, JournalEntryAssociationParametersData associationParametersData);
OfficeOpeningBalancesData retrieveOfficeOpeningBalances(Long officeId);
} }
@@ -10,24 +10,35 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.accounting.common.AccountingEnumerations; import org.mifosplatform.accounting.common.AccountingEnumerations;
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
import org.mifosplatform.accounting.glaccount.domain.GLAccountType;
import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService;
import org.mifosplatform.accounting.journalentry.data.JournalEntryAssociationParametersData; import org.mifosplatform.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.mifosplatform.accounting.journalentry.data.JournalEntryData; import org.mifosplatform.accounting.journalentry.data.JournalEntryData;
import org.mifosplatform.accounting.journalentry.data.OfficeOpeningBalancesData;
import org.mifosplatform.accounting.journalentry.data.TransactionDetailData; import org.mifosplatform.accounting.journalentry.data.TransactionDetailData;
import org.mifosplatform.accounting.journalentry.data.TransactionTypeEnumData; import org.mifosplatform.accounting.journalentry.data.TransactionTypeEnumData;
import org.mifosplatform.accounting.journalentry.exception.JournalEntriesNotFoundException; import org.mifosplatform.accounting.journalentry.exception.JournalEntriesNotFoundException;
import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.core.service.PaginationHelper; import org.mifosplatform.infrastructure.core.service.PaginationHelper;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.organisation.monetary.data.CurrencyData; 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.account.PortfolioAccountType; import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.infrastructure.core.service.SearchParameters; import org.mifosplatform.infrastructure.core.service.SearchParameters;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
@@ -41,17 +52,26 @@ import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@Service @Service
public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlatformService { public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlatformService {
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final ConfigurationDomainService configurationDomainService;
private final GLAccountReadPlatformService glAccountReadPlatformService;
private final OfficeReadPlatformService officeReadPlatformService;
private final PaginationHelper<JournalEntryData> paginationHelper = new PaginationHelper<>(); private final PaginationHelper<JournalEntryData> paginationHelper = new PaginationHelper<>();
@Autowired @Autowired
public JournalEntryReadPlatformServiceImpl(final RoutingDataSource dataSource) { public JournalEntryReadPlatformServiceImpl(final RoutingDataSource dataSource,
final ConfigurationDomainService configurationDomainService, final GLAccountReadPlatformService glAccountReadPlatformService,
final OfficeReadPlatformService officeReadPlatformService) {
this.jdbcTemplate = new JdbcTemplate(dataSource); this.jdbcTemplate = new JdbcTemplate(dataSource);
this.configurationDomainService = configurationDomainService;
this.glAccountReadPlatformService = glAccountReadPlatformService;
this.officeReadPlatformService = officeReadPlatformService;
} }
private static final class GLJournalEntryMapper implements RowMapper<JournalEntryData> { private static final class GLJournalEntryMapper implements RowMapper<JournalEntryData> {
@@ -333,4 +353,124 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
} }
} }
@Override
public OfficeOpeningBalancesData retrieveOfficeOpeningBalances(final Long officeId) {
/**
* Global configuration 'office-opening-balances-contra-account'
* property value must not be null and it should be a equity account.
*/
final Long contraId = this.configurationDomainService.retrieveOpeningBalancesContraAccount();
if (contraId == null) { throw new GeneralPlatformDomainRuleException(
"error.msg.configuration.opening.balance.contra.account.cannot.be.null",
"Configuration property 'office-opening-balances-contra-account' value can not be null",
"office-opening-balances-contra-account"); }
final JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData();
final GLAccountData contraAccount = this.glAccountReadPlatformService.retrieveGLAccountById(contraId, associationParametersData);
if (!GLAccountType.fromInt(contraAccount.getTypeId()).isEquityType()) { throw new GeneralPlatformDomainRuleException(
"error.msg.configuration.opening.balance.contra.account.value.is.invalid.account.type",
"Global configuration 'office-opening-balances-contra-account' value is not an equity type account", contraId); }
final OfficeData officeData = this.officeReadPlatformService.retrieveOffice(officeId);
final List<JournalEntryData> allOpeningTransactions = populateAllTransactionsFromGLAccounts(contraId);
final String contraTransactionId = retrieveContraAccountTransactionId(officeId, contraId);
List<JournalEntryData> existingOpeningBalanceTransactions = new ArrayList<>();
if (StringUtils.isNotBlank(contraTransactionId)) {
existingOpeningBalanceTransactions = retrieveOfficeBalanceTransactions(officeId, contraTransactionId);
}
final List<JournalEntryData> transactions = populateOpeningBalances(existingOpeningBalanceTransactions, allOpeningTransactions);
final List<JournalEntryData> assetAccountOpeningBalances = new ArrayList<>();
final List<JournalEntryData> liabityAccountOpeningBalances = new ArrayList<>();
final List<JournalEntryData> incomeAccountOpeningBalances = new ArrayList<>();
final List<JournalEntryData> equityAccountOpeningBalances = new ArrayList<>();
final List<JournalEntryData> expenseAccountOpeningBalances = new ArrayList<>();
for (final JournalEntryData journalEntryData : transactions) {
final GLAccountType type = GLAccountType.fromInt(journalEntryData.getGlAccountType().getId().intValue());
if (type.isAssetType()) {
assetAccountOpeningBalances.add(journalEntryData);
} else if (type.isLiabilityType()) {
liabityAccountOpeningBalances.add(journalEntryData);
} else if (type.isEquityType()) {
equityAccountOpeningBalances.add(journalEntryData);
} else if (type.isIncomeType()) {
incomeAccountOpeningBalances.add(journalEntryData);
} else if (type.isExpenseType()) {
expenseAccountOpeningBalances.add(journalEntryData);
}
}
final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
final OfficeOpeningBalancesData officeOpeningBalancesData = OfficeOpeningBalancesData.createNew(officeId, officeData.name(),
transactionDate, contraAccount, assetAccountOpeningBalances, liabityAccountOpeningBalances, incomeAccountOpeningBalances,
equityAccountOpeningBalances, expenseAccountOpeningBalances);
return officeOpeningBalancesData;
}
private List<JournalEntryData> populateOpeningBalances(final List<JournalEntryData> existingOpeningBalanceTransactions,
final List<JournalEntryData> allOpeningTransactions) {
final List<JournalEntryData> allOpeningBalnceTransactions = new ArrayList<>(allOpeningTransactions.size());
for (final JournalEntryData newOpeningBalanceTransaction : allOpeningTransactions) {
boolean isNewTransactionAddedToCollection = false;
for (final JournalEntryData existingOpeningBalanceTransaction : existingOpeningBalanceTransactions) {
if (newOpeningBalanceTransaction.getGlAccountId().equals(existingOpeningBalanceTransaction.getGlAccountId())) {
allOpeningBalnceTransactions.add(existingOpeningBalanceTransaction);
isNewTransactionAddedToCollection = true;
break;
}
}
if (!isNewTransactionAddedToCollection) {
allOpeningBalnceTransactions.add(newOpeningBalanceTransaction);
}
}
return allOpeningBalnceTransactions;
}
private List<JournalEntryData> populateAllTransactionsFromGLAccounts(final Long contraId) {
final List<GLAccountData> glAccounts = this.glAccountReadPlatformService.retrieveAllEnabledDetailGLAccounts();
final List<JournalEntryData> openingBalanceTransactions = new ArrayList<>(glAccounts.size());
for (final GLAccountData glAccountData : glAccounts) {
if (!contraId.equals(glAccountData.getId())) {
final JournalEntryData openingBalanceTransaction = JournalEntryData.fromGLAccountData(glAccountData);
openingBalanceTransactions.add(openingBalanceTransaction);
}
}
return openingBalanceTransactions;
}
private List<JournalEntryData> retrieveOfficeBalanceTransactions(final Long officeId, final String transactionId) {
final Long contraId = null;
return retrieveContraTransactions(officeId, contraId, transactionId).getPageItems();
}
private String retrieveContraAccountTransactionId(final Long officeId, final Long contraId) {
final String transactionId = "";
final Page<JournalEntryData> contraJournalEntries = retrieveContraTransactions(officeId, contraId, transactionId);
if (!CollectionUtils.isEmpty(contraJournalEntries.getPageItems())) {
final JournalEntryData contraTransaction = contraJournalEntries.getPageItems().get(contraJournalEntries.getPageItems().size()-1);
return contraTransaction.getTransactionId();
}
return transactionId;
}
private Page<JournalEntryData> retrieveContraTransactions(final Long officeId, final Long contraId, final String transactionId) {
final Integer offset = 0;
final Integer limit = null;
final String orderBy = "journalEntry.id";
final String sortOrder = "ASC";
final Integer entityType = null;
final Boolean onlyManualEntries = null;
final Date fromDate = null;
final Date toDate = null;
final JournalEntryAssociationParametersData associationParametersData = null;
final SearchParameters searchParameters = SearchParameters.forJournalEntries(officeId, offset, limit, orderBy, sortOrder);
return retrieveAll(searchParameters, contraId, onlyManualEntries, fromDate, toDate, transactionId, entityType,
associationParametersData);
}
} }
@@ -19,4 +19,7 @@ public interface JournalEntryWritePlatformService {
void createJournalEntriesForLoan(Map<String, Object> accountingBridgeData); void createJournalEntriesForLoan(Map<String, Object> accountingBridgeData);
void createJournalEntriesForSavings(Map<String, Object> accountingBridgeData); void createJournalEntriesForSavings(Map<String, Object> accountingBridgeData);
CommandProcessingResult defineOpeningBalance(JsonCommand command);
} }
@@ -19,6 +19,7 @@ import org.mifosplatform.accounting.closure.domain.GLClosureRepository;
import org.mifosplatform.accounting.glaccount.data.GLAccountDataForLookup; import org.mifosplatform.accounting.glaccount.data.GLAccountDataForLookup;
import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.accounting.glaccount.domain.GLAccount;
import org.mifosplatform.accounting.glaccount.domain.GLAccountRepository; import org.mifosplatform.accounting.glaccount.domain.GLAccountRepository;
import org.mifosplatform.accounting.glaccount.domain.GLAccountType;
import org.mifosplatform.accounting.glaccount.exception.GLAccountNotFoundException; import org.mifosplatform.accounting.glaccount.exception.GLAccountNotFoundException;
import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService; import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService;
import org.mifosplatform.accounting.journalentry.api.JournalEntryJsonInputParams; import org.mifosplatform.accounting.journalentry.api.JournalEntryJsonInputParams;
@@ -36,11 +37,13 @@ import org.mifosplatform.accounting.journalentry.serialization.JournalEntryComma
import org.mifosplatform.accounting.rule.domain.AccountingRule; import org.mifosplatform.accounting.rule.domain.AccountingRule;
import org.mifosplatform.accounting.rule.domain.AccountingRuleRepository; import org.mifosplatform.accounting.rule.domain.AccountingRuleRepository;
import org.mifosplatform.accounting.rule.exception.AccountingRuleNotFoundException; import org.mifosplatform.accounting.rule.exception.AccountingRuleNotFoundException;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
@@ -57,6 +60,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@Service @Service
public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements JournalEntryWritePlatformService { public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements JournalEntryWritePlatformService {
@@ -76,6 +80,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository; private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
private final ConfigurationDomainService configurationDomainService;
@Autowired @Autowired
public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository, public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository,
@@ -86,7 +91,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory, final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory,
final GLAccountReadPlatformService glAccountReadPlatformService, final GLAccountReadPlatformService glAccountReadPlatformService,
final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository, final PlatformSecurityContext context, final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository, final PlatformSecurityContext context,
final PaymentDetailWritePlatformService paymentDetailWritePlatformService) { final PaymentDetailWritePlatformService paymentDetailWritePlatformService,
final ConfigurationDomainService configurationDomainService) {
this.glClosureRepository = glClosureRepository; this.glClosureRepository = glClosureRepository;
this.officeRepository = officeRepository; this.officeRepository = officeRepository;
this.glJournalEntryRepository = glJournalEntryRepository; this.glJournalEntryRepository = glJournalEntryRepository;
@@ -100,6 +106,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
this.organisationCurrencyRepository = organisationCurrencyRepository; this.organisationCurrencyRepository = organisationCurrencyRepository;
this.context = context; this.context = context;
this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
this.configurationDomainService = configurationDomainService;
} }
@Transactional @Transactional
@@ -277,12 +284,18 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
// is the transaction Id valid // is the transaction Id valid
final List<JournalEntry> journalEntries = this.glJournalEntryRepository.findUnReversedManualJournalEntriesByTransactionId(command final List<JournalEntry> journalEntries = this.glJournalEntryRepository.findUnReversedManualJournalEntriesByTransactionId(command
.getTransactionId()); .getTransactionId());
String reversalComment = command.stringValueOfParameterNamed("comments");
if (journalEntries.size() <= 1) { throw new JournalEntriesNotFoundException(command.getTransactionId()); } if (journalEntries.size() <= 1) { throw new JournalEntriesNotFoundException(command.getTransactionId()); }
final String reversalTransactionId = revertJournalEntry(journalEntries, reversalComment);
return new CommandProcessingResultBuilder().withTransactionId(reversalTransactionId).build();
}
public String revertJournalEntry(final List<JournalEntry> journalEntries, String reversalComment) {
final Long officeId = journalEntries.get(0).getOffice().getId(); final Long officeId = journalEntries.get(0).getOffice().getId();
final String reversalTransactionId = generateTransactionId(officeId); final String reversalTransactionId = generateTransactionId(officeId);
final boolean manualEntry = true; final boolean manualEntry = true;
String reversalComment = command.stringValueOfParameterNamed("comments");
final boolean useDefaultComment = StringUtils.isBlank(reversalComment); final boolean useDefaultComment = StringUtils.isBlank(reversalComment);
validateCommentForReversal(reversalComment); validateCommentForReversal(reversalComment);
@@ -290,8 +303,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
for (final JournalEntry journalEntry : journalEntries) { for (final JournalEntry journalEntry : journalEntries) {
JournalEntry reversalJournalEntry; JournalEntry reversalJournalEntry;
if (useDefaultComment) { if (useDefaultComment) {
reversalComment = "Reversal entry for Journal Entry with Entry Id :" + journalEntry.getId() reversalComment = "Reversal entry for Journal Entry with Entry Id :" + journalEntry.getId() + " and transaction Id "
+ " and transaction Id " + command.getTransactionId(); + journalEntry.getTransactionId();
} }
if (journalEntry.isDebitEntry()) { if (journalEntry.isDebitEntry()) {
reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(), reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(),
@@ -311,7 +324,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
// save the updated journal entry // save the updated journal entry
this.glJournalEntryRepository.saveAndFlush(journalEntry); this.glJournalEntryRepository.saveAndFlush(journalEntry);
} }
return new CommandProcessingResultBuilder().withTransactionId(reversalTransactionId).build(); return reversalTransactionId;
} }
private void validateCommentForReversal(final String reversalComment) { private void validateCommentForReversal(final String reversalComment) {
@@ -430,4 +443,117 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
"Unknown data integrity issue with resource Journal Entry: " + realCause.getMessage()); "Unknown data integrity issue with resource Journal Entry: " + realCause.getMessage());
} }
@Transactional
@Override
public CommandProcessingResult defineOpeningBalance(final JsonCommand command) {
try {
final JournalEntryCommand journalEntryCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
journalEntryCommand.validateForCreate();
/**
* Global configuration 'office-opening-balances-contra-account'
* property value must not be null and it should be a equity
* account.
*/
final Long contraId = this.configurationDomainService.retrieveOpeningBalancesContraAccount();
if (contraId == null) { throw new GeneralPlatformDomainRuleException(
"error.msg.configuration.opening.balance.contra.account.cannot.be.null",
"Configuration property 'office-opening-balances-contra-account' value can not be null",
"office-opening-balances-contra-account"); }
validateJournalEntriesArePostedBefore(contraId);
// check office is valid
final Long officeId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.OFFICE_ID.getValue());
final Office office = this.officeRepository.findOne(officeId);
if (office == null) { throw new OfficeNotFoundException(officeId); }
final String currencyCode = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.CURRENCY_CODE.getValue());
validateBusinessRulesForJournalEntries(journalEntryCommand);
/**
* revert old journal entries
*/
final List<String> transactionIdsToBeReversed = this.glJournalEntryRepository.findNonReversedContraTansactionIds(contraId,
officeId);
for (String transactionId : transactionIdsToBeReversed) {
final List<JournalEntry> journalEntries = this.glJournalEntryRepository
.findUnReversedManualJournalEntriesByTransactionId(transactionId);
revertJournalEntry(journalEntries, "defining opening balance");
}
/** Set a transaction Id and save these Journal entries **/
final Date transactionDate = command.DateValueOfParameterNamed(JournalEntryJsonInputParams.TRANSACTION_DATE.getValue());
final String transactionId = generateTransactionId(officeId);
saveAllDebitOrCreditOpeningBalanceEntries(journalEntryCommand, office, currencyCode, transactionDate,
journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, contraId);
saveAllDebitOrCreditOpeningBalanceEntries(journalEntryCommand, office, currencyCode, transactionDate,
journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, contraId);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
.withTransactionId(transactionId).build();
} catch (final DataIntegrityViolationException dve) {
handleJournalEntryDataIntegrityIssues(dve);
return null;
}
}
private void saveAllDebitOrCreditOpeningBalanceEntries(final JournalEntryCommand command, final Office office,
final String currencyCode, final Date transactionDate,
final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, final String transactionId,
final JournalEntryType type, final Long contraAccountId) {
final boolean manualEntry = true;
final GLAccount contraAccount = this.glAccountRepository.findOne(contraAccountId);
if (contraAccount == null) { throw new GLAccountNotFoundException(contraAccountId); }
if (!GLAccountType.fromInt(contraAccount.getType()).isEquityType()) { throw new GeneralPlatformDomainRuleException(
"error.msg.configuration.opening.balance.contra.account.value.is.invalid.account.type",
"Global configuration 'office-opening-balances-contra-account' value is not an equity type account", contraAccountId); }
validateGLAccountForTransaction(contraAccount);
final JournalEntryType contraType = getContraType(type);
String comments = command.getComments();
/** Validate current code is appropriate **/
this.organisationCurrencyRepository.findOneWithNotFoundDetection(currencyCode);
for (final SingleDebitOrCreditEntryCommand singleDebitOrCreditEntryCommand : singleDebitOrCreditEntryCommands) {
final GLAccount glAccount = this.glAccountRepository.findOne(singleDebitOrCreditEntryCommand.getGlAccountId());
if (glAccount == null) { throw new GLAccountNotFoundException(singleDebitOrCreditEntryCommand.getGlAccountId()); }
validateGLAccountForTransaction(glAccount);
if (!StringUtils.isBlank(singleDebitOrCreditEntryCommand.getComments())) {
comments = singleDebitOrCreditEntryCommand.getComments();
}
final JournalEntry glJournalEntry = JournalEntry.createNew(office, null, glAccount, currencyCode, transactionId, manualEntry,
transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, null, null, null);
this.glJournalEntryRepository.saveAndFlush(glJournalEntry);
final JournalEntry contraEntry = JournalEntry.createNew(office, null, contraAccount, currencyCode, transactionId, manualEntry,
transactionDate, contraType, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, null, null, null);
this.glJournalEntryRepository.saveAndFlush(contraEntry);
}
}
private JournalEntryType getContraType(final JournalEntryType type) {
final JournalEntryType contraType;
if (type.isCreditType()) {
contraType = JournalEntryType.DEBIT;
} else {
contraType = JournalEntryType.CREDIT;
}
return contraType;
}
private void validateJournalEntriesArePostedBefore(final Long contraId) {
final List<String> transactionIds = this.glJournalEntryRepository.findNonContraTansactionIds(contraId);
if (!CollectionUtils.isEmpty(transactionIds)) { throw new GeneralPlatformDomainRuleException(
"error.msg.journalentry.defining.openingbalance.not.allowed",
"Defining Opening balances not allowed after journal entries posted", transactionIds); }
}
} }
@@ -323,6 +323,14 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("UPDATERUNNINGBALANCE") && this.entityName.equalsIgnoreCase("JOURNALENTRY"); return this.actionName.equalsIgnoreCase("UPDATERUNNINGBALANCE") && this.entityName.equalsIgnoreCase("JOURNALENTRY");
} }
public boolean isDefineOpeningalance() {
return this.actionName.equalsIgnoreCase("DEFINEOPENINGBALANCE") && this.entityName.equalsIgnoreCase("JOURNALENTRY");
}
public boolean isUpdateOpeningbalance() {
return this.actionName.equalsIgnoreCase("UPDATEOPENINGBALANCE") && this.entityName.equalsIgnoreCase("JOURNALENTRY");
}
public boolean isFundResource() { public boolean isFundResource() {
return this.entityName.equalsIgnoreCase("FUND"); return this.entityName.equalsIgnoreCase("FUND");
} }
@@ -944,6 +944,22 @@ public class CommandWrapperBuilder {
return this; return this;
} }
public CommandWrapperBuilder defineOpeningBalanceForJournalEntry() {
this.actionName = "DEFINEOPENINGBALANCE";
this.entityName = "JOURNALENTRY";
this.entityId = null;
this.href = "/journalentries/update";
return this;
}
public CommandWrapperBuilder updateOpeningBalanceForJournalEntry() {
this.actionName = "UPDATEOPENINGBALANCE";
this.entityName = "JOURNALENTRY";
this.entityId = null;
this.href = "/journalentries/update";
return this;
}
public CommandWrapperBuilder createSavingProduct() { public CommandWrapperBuilder createSavingProduct() {
this.actionName = "CREATE"; this.actionName = "CREATE";
this.entityName = "SAVINGSPRODUCT"; this.entityName = "SAVINGSPRODUCT";
@@ -472,6 +472,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("reverseJournalEntryCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("reverseJournalEntryCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateRunningbalance()) { } else if (wrapper.isUpdateRunningbalance()) {
handler = this.applicationContext.getBean("updateRunningBalanceCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("updateRunningBalanceCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDefineOpeningalance()) {
handler = this.applicationContext.getBean("defineOpeningBalanceCommandHandler", NewCommandSourceHandler.class);
} else { } else {
throw new UnsupportedCommandException(wrapper.commandName()); throw new UnsupportedCommandException(wrapper.commandName());
} }
@@ -35,6 +35,8 @@ public interface ConfigurationDomainService {
Long retrieveGraceOnPenaltyPostingPeriod(); Long retrieveGraceOnPenaltyPostingPeriod();
Long retrieveOpeningBalancesContraAccount();
boolean isSavingsInterestPostingAtCurrentPeriodEnd(); boolean isSavingsInterestPostingAtCurrentPeriodEnd();
Integer retrieveFinancialYearBeginningMonth(); Integer retrieveFinancialYearBeginningMonth();
@@ -135,6 +135,13 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
return property.getValue(); return property.getValue();
} }
@Override
public Long retrieveOpeningBalancesContraAccount() {
final String propertyName = "office-opening-balances-contra-account";
final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
return property.getValue();
}
@Override @Override
public boolean isSavingsInterestPostingAtCurrentPeriodEnd() { public boolean isSavingsInterestPostingAtCurrentPeriodEnd() {
final String propertyName = "savings-interest-posting-current-period-end"; final String propertyName = "savings-interest-posting-current-period-end";
@@ -0,0 +1 @@
INSERT INTO `m_code` (`code_name`,`is_system_defined`) VALUES('CenterClosureReason',1);
@@ -0,0 +1,34 @@
-- Example: INSERT INTO `acc_gl_account`
-- (
-- `name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`,
-- `account_usage`, `classification_enum`, `tag_id`, `description`
-- )
-- VALUES
-- (
-- 'Opening Balances Contra Account', NULL, '.', 'OBCA', 0, 1,
-- 1, 3, NULL, NULL
-- );
INSERT INTO `c_configuration`
(
`name`,
`value`,
`enabled`
)
VALUES
(
'office-opening-balances-contra-account',
0, -- Or Example: (SELECT id FROM acc_gl_account WHERE gl_code = 'OBCA' ),
1);
ALTER TABLE `c_configuration`
ADD UNIQUE INDEX `name_UNIQUE` (`name`);
INSERT INTO `m_permission`
(
`grouping`, `code`, `entity_name`, `action_name`
)
VALUES
(
'accounting', 'DEFINEOPENINGBALANCE_JOURNALENTRY', 'JOURNALENTRY', 'DEFINEOPENINGBALANCE'
);