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;
}
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.mifosplatform.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.mifosplatform.accounting.journalentry.data.JournalEntryData;
import org.mifosplatform.accounting.journalentry.data.OfficeOpeningBalancesData;
import org.mifosplatform.accounting.journalentry.service.JournalEntryReadPlatformService;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
@@ -53,7 +54,7 @@ public class JournalEntriesApiResource {
private final String resourceNameForPermission = "JOURNALENTRY";
private final JournalEntryReadPlatformService journalEntryReadPlatformService;
private final DefaultToApiJsonSerializer<JournalEntryData> apiJsonSerializerService;
private final DefaultToApiJsonSerializer<Object> apiJsonSerializerService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PlatformSecurityContext context;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@@ -61,7 +62,7 @@ public class JournalEntriesApiResource {
@Autowired
public JournalEntriesApiResource(final PlatformSecurityContext context,
final JournalEntryReadPlatformService journalEntryReadPlatformService,
final DefaultToApiJsonSerializer<JournalEntryData> toApiJsonSerializer,
final DefaultToApiJsonSerializer<Object> toApiJsonSerializer,
final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.context = context;
@@ -131,6 +132,10 @@ public class JournalEntriesApiResource {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRunningBalanceForJournalEntry()
.withJson(jsonRequestBody).build();
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 {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createJournalEntry().withJson(jsonRequestBody).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
@@ -156,6 +161,18 @@ public class JournalEntriesApiResource {
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) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
@@ -8,6 +8,7 @@ package org.mifosplatform.accounting.journalentry.data;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
@@ -99,6 +100,39 @@ public class JournalEntryData {
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() {
return this.id;
}
@@ -122,4 +156,8 @@ public class JournalEntryData {
public Long getOfficeId() {
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;
}
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")
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.JournalEntryData;
import org.mifosplatform.accounting.journalentry.data.OfficeOpeningBalancesData;
import org.mifosplatform.infrastructure.core.service.Page;
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,
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.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
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.JournalEntryData;
import org.mifosplatform.accounting.journalentry.data.OfficeOpeningBalancesData;
import org.mifosplatform.accounting.journalentry.data.TransactionDetailData;
import org.mifosplatform.accounting.journalentry.data.TransactionTypeEnumData;
import org.mifosplatform.accounting.journalentry.exception.JournalEntriesNotFoundException;
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.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.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.account.PortfolioAccountType;
import org.mifosplatform.infrastructure.core.service.SearchParameters;
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.RowMapper;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@Service
public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final ConfigurationDomainService configurationDomainService;
private final GLAccountReadPlatformService glAccountReadPlatformService;
private final OfficeReadPlatformService officeReadPlatformService;
private final PaginationHelper<JournalEntryData> paginationHelper = new PaginationHelper<>();
@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.configurationDomainService = configurationDomainService;
this.glAccountReadPlatformService = glAccountReadPlatformService;
this.officeReadPlatformService = officeReadPlatformService;
}
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 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.domain.GLAccount;
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.service.GLAccountReadPlatformService;
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.AccountingRuleRepository;
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.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
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.PlatformDataIntegrityException;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@Service
public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements JournalEntryWritePlatformService {
@@ -76,6 +80,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository;
private final PlatformSecurityContext context;
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
private final ConfigurationDomainService configurationDomainService;
@Autowired
public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository,
@@ -86,7 +91,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory,
final GLAccountReadPlatformService glAccountReadPlatformService,
final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository, final PlatformSecurityContext context,
final PaymentDetailWritePlatformService paymentDetailWritePlatformService) {
final PaymentDetailWritePlatformService paymentDetailWritePlatformService,
final ConfigurationDomainService configurationDomainService) {
this.glClosureRepository = glClosureRepository;
this.officeRepository = officeRepository;
this.glJournalEntryRepository = glJournalEntryRepository;
@@ -100,6 +106,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
this.organisationCurrencyRepository = organisationCurrencyRepository;
this.context = context;
this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
this.configurationDomainService = configurationDomainService;
}
@Transactional
@@ -277,12 +284,18 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
// is the transaction Id valid
final List<JournalEntry> journalEntries = this.glJournalEntryRepository.findUnReversedManualJournalEntriesByTransactionId(command
.getTransactionId());
String reversalComment = command.stringValueOfParameterNamed("comments");
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 String reversalTransactionId = generateTransactionId(officeId);
final boolean manualEntry = true;
String reversalComment = command.stringValueOfParameterNamed("comments");
final boolean useDefaultComment = StringUtils.isBlank(reversalComment);
validateCommentForReversal(reversalComment);
@@ -290,8 +303,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
for (final JournalEntry journalEntry : journalEntries) {
JournalEntry reversalJournalEntry;
if (useDefaultComment) {
reversalComment = "Reversal entry for Journal Entry with Entry Id :" + journalEntry.getId()
+ " and transaction Id " + command.getTransactionId();
reversalComment = "Reversal entry for Journal Entry with Entry Id :" + journalEntry.getId() + " and transaction Id "
+ journalEntry.getTransactionId();
}
if (journalEntry.isDebitEntry()) {
reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(),
@@ -311,7 +324,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
// save the updated journal entry
this.glJournalEntryRepository.saveAndFlush(journalEntry);
}
return new CommandProcessingResultBuilder().withTransactionId(reversalTransactionId).build();
return reversalTransactionId;
}
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());
}
@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");
}
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() {
return this.entityName.equalsIgnoreCase("FUND");
}
@@ -944,6 +944,22 @@ public class CommandWrapperBuilder {
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() {
this.actionName = "CREATE";
this.entityName = "SAVINGSPRODUCT";
@@ -472,6 +472,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("reverseJournalEntryCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateRunningbalance()) {
handler = this.applicationContext.getBean("updateRunningBalanceCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDefineOpeningalance()) {
handler = this.applicationContext.getBean("defineOpeningBalanceCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -35,6 +35,8 @@ public interface ConfigurationDomainService {
Long retrieveGraceOnPenaltyPostingPeriod();
Long retrieveOpeningBalancesContraAccount();
boolean isSavingsInterestPostingAtCurrentPeriodEnd();
Integer retrieveFinancialYearBeginningMonth();
@@ -135,6 +135,13 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
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
public boolean isSavingsInterestPostingAtCurrentPeriodEnd() {
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'
);