From c896e3d1bdfa432cf1834f9d1b5f6c47d775fbfa Mon Sep 17 00:00:00 2001 From: mifoscontributer Date: Mon, 20 May 2013 19:11:40 +0530 Subject: [PATCH] Making JournalEntries With predefined accounting rule Fixing issues with constructor for journalEntryCommand --- .../api/JournalEntryJsonInputParams.java | 2 +- .../command/JournalEntryCommand.java | 84 ++++++++---- ...alEntryCommandFromApiJsonDeserializer.java | 9 +- ...WritePlatformServiceJpaRepositoryImpl.java | 122 ++++++++++++------ .../rule/domain/AccountingRule.java | 4 + 5 files changed, 152 insertions(+), 69 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/api/JournalEntryJsonInputParams.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/api/JournalEntryJsonInputParams.java index efae90ae0..344fc4d6a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/api/JournalEntryJsonInputParams.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/api/JournalEntryJsonInputParams.java @@ -13,7 +13,7 @@ import java.util.Set; ***/ public enum JournalEntryJsonInputParams { OFFICE_ID("officeId"), TRANSACTION_DATE("transactionDate"), COMMENTS("comments"), CREDITS("credits"), DEBITS("debits"), LOCALE("locale"), DATE_FORMAT( - "dateFormat"), REFERENCE_NUMBER("referenceNumber") ; + "dateFormat"), REFERENCE_NUMBER("referenceNumber"),IS_PREDEFINED_RULE_ENTRY("isPredefinedRuleEntry"),ACCOUNTING_RULE("accountingRule"), AMOUNT("amount"); private final String value; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/command/JournalEntryCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/command/JournalEntryCommand.java index e1ca55109..923df25dc 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/command/JournalEntryCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/command/JournalEntryCommand.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.accounting.journalentry.command; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -22,18 +23,35 @@ public class JournalEntryCommand { private final LocalDate transactionDate; private final String comments; private final String referenceNumber; + private final Boolean isPredefinedRuleEntry; + private final Long accountingRuleId; + private final BigDecimal amount; private final SingleDebitOrCreditEntryCommand[] credits; private final SingleDebitOrCreditEntryCommand[] debits; public JournalEntryCommand(final Long officeId, final LocalDate transactionDate, final String comments, - final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits, final String referenceNumber) { + final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits, + final String referenceNumber, final Boolean isPredefinedRuleEntry, final Long accountingRuleId, final BigDecimal amount) { this.officeId = officeId; this.transactionDate = transactionDate; this.comments = comments; this.credits = credits; this.debits = debits; this.referenceNumber = referenceNumber; + this.isPredefinedRuleEntry = isPredefinedRuleEntry; + this.accountingRuleId = accountingRuleId; + this.amount = amount; + } + + public JournalEntryCommand(final Long officeId, final LocalDate transactionDate, final String comments, final String referenceNumber, + final Boolean isPredefinedRuleEntry, final Long accountingRuleId, final BigDecimal amount) { + this(officeId, transactionDate, comments, null, null, referenceNumber, isPredefinedRuleEntry, accountingRuleId, amount); + } + + public JournalEntryCommand(Long officeId, LocalDate transactionDate, String comments, SingleDebitOrCreditEntryCommand[] credits, + SingleDebitOrCreditEntryCommand[] debits, String referenceNumber, Boolean isPredefinedRuleEntry) { + this(officeId, transactionDate, comments, credits, debits, referenceNumber, isPredefinedRuleEntry, null,null); } public void validateForCreate() { @@ -47,37 +65,45 @@ public class JournalEntryCommand { baseDataValidator.reset().parameter("officeId").value(this.officeId).notNull().integerGreaterThanZero(); baseDataValidator.reset().parameter("comments").value(this.comments).ignoreIfNull().notExceedingLengthOf(500); - - baseDataValidator.reset().parameter("credits").value(this.credits).notNull(); - - baseDataValidator.reset().parameter("debits").value(this.debits).notNull(); - + + baseDataValidator.reset().parameter("isPredefinedRuleEntry").value(this.isPredefinedRuleEntry).notNull(); + baseDataValidator.reset().parameter("referenceNumber").value(this.referenceNumber).ignoreIfNull().notExceedingLengthOf(100); - - // validation for credit array elements - if (this.credits != null) { - if (this.credits.length == 0) { - validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null)); - } else { - int i = 0; - for (final SingleDebitOrCreditEntryCommand credit : this.credits) { - validateSingleDebitOrCredit(baseDataValidator, "credits", i, credit); - i++; + + if (!this.isPredefinedRuleEntry) { + + baseDataValidator.reset().parameter("credits").value(this.credits).notNull(); + + baseDataValidator.reset().parameter("debits").value(this.debits).notNull(); + + // validation for credit array elements + if (this.credits != null) { + if (this.credits.length == 0) { + validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null)); + } else { + int i = 0; + for (final SingleDebitOrCreditEntryCommand credit : this.credits) { + validateSingleDebitOrCredit(baseDataValidator, "credits", i, credit); + i++; + } } } - } - - // validation for debit array elements - if (this.debits != null) { - if (this.debits.length == 0) { - validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null)); - } else { - int i = 0; - for (final SingleDebitOrCreditEntryCommand debit : this.debits) { - validateSingleDebitOrCredit(baseDataValidator, "debits", i, debit); - i++; + + // validation for debit array elements + if (this.debits != null) { + if (this.debits.length == 0) { + validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null)); + } else { + int i = 0; + for (final SingleDebitOrCreditEntryCommand debit : this.debits) { + validateSingleDebitOrCredit(baseDataValidator, "debits", i, debit); + i++; + } } } + } else { + baseDataValidator.reset().parameter("accountingRule").value(this.accountingRuleId).notNull().longGreaterThanZero(); + baseDataValidator.reset().parameter("amount").value(this.amount).notNull().zeroOrPositiveAmount(); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", @@ -121,4 +147,8 @@ public class JournalEntryCommand { return this.referenceNumber; } + public Boolean getIsPredefinedRuleEntry() { + return this.isPredefinedRuleEntry; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java index 105d70523..d441b4325 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java @@ -59,10 +59,17 @@ public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFr JournalEntryJsonInputParams.TRANSACTION_DATE.getValue(), element); final String referenceNumber = this.fromApiJsonHelper.extractStringNamed( JournalEntryJsonInputParams.REFERENCE_NUMBER.getValue(), element); + final Boolean isPredefinedRuleEntry = this.fromApiJsonHelper.extractBooleanNamed(JournalEntryJsonInputParams.IS_PREDEFINED_RULE_ENTRY.getValue(), element); final JsonObject topLevelJsonElement = element.getAsJsonObject(); final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if(isPredefinedRuleEntry) { + final Long accountingRuleId = this.fromApiJsonHelper.extractLongNamed(JournalEntryJsonInputParams.ACCOUNTING_RULE.getValue(), element); + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(JournalEntryJsonInputParams.AMOUNT.getValue(), element, locale); + return new JournalEntryCommand(officeId, transactionDate, comments, referenceNumber, isPredefinedRuleEntry, accountingRuleId, amount); + } + SingleDebitOrCreditEntryCommand[] credits = null; SingleDebitOrCreditEntryCommand[] debits = null; if (element.isJsonObject()) { @@ -75,7 +82,7 @@ public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFr debits = populateCreditsOrDebitsArray(topLevelJsonElement, locale, debits, JournalEntryJsonInputParams.DEBITS.getValue()); } } - return new JournalEntryCommand(officeId, transactionDate, comments, credits, debits,referenceNumber); + return new JournalEntryCommand(officeId, transactionDate, comments, credits, debits, referenceNumber, isPredefinedRuleEntry); } /** diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index b2f9170ba..2fd1dc289 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -29,6 +29,9 @@ import org.mifosplatform.accounting.journalentry.exception.JournalEntriesNotFoun import org.mifosplatform.accounting.journalentry.exception.JournalEntryInvalidException; import org.mifosplatform.accounting.journalentry.exception.JournalEntryInvalidException.GL_JOURNAL_ENTRY_INVALID_REASON; import org.mifosplatform.accounting.journalentry.serialization.JournalEntryCommandFromApiJsonDeserializer; +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.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -55,12 +58,13 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa private final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory; private final AccountingProcessorHelper helper; private final JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer; + private final AccountingRuleRepository accountingRuleRepository; @Autowired public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository, final JournalEntryRepository glJournalEntryRepository, final OfficeRepository officeRepository, final GLAccountRepository glAccountRepository, final JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer, - final AccountingProcessorHelper accountingProcessorHelper, + final AccountingProcessorHelper accountingProcessorHelper, final AccountingRuleRepository accountingRuleRepository, final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory) { this.glClosureRepository = glClosureRepository; this.officeRepository = officeRepository; @@ -69,6 +73,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa this.glAccountRepository = glAccountRepository; this.accountingProcessorForLoanFactory = accountingProcessorForLoanFactory; this.helper = accountingProcessorHelper; + this.accountingRuleRepository = accountingRuleRepository; } @Transactional @@ -82,18 +87,50 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final Long officeId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.OFFICE_ID.getValue()); final Office office = this.officeRepository.findOne(officeId); if (office == null) { throw new OfficeNotFoundException(officeId); } - + + final boolean isPredefinedRuleEntry = command.booleanPrimitiveValueOfParameterNamed(JournalEntryJsonInputParams.IS_PREDEFINED_RULE_ENTRY.getValue()); + validateBusinessRulesForJournalEntries(journalEntryCommand); /** Set a transaction Id and save these Journal entries **/ final Date transactionDate = command.DateValueOfParameterNamed(JournalEntryJsonInputParams.TRANSACTION_DATE.getValue()); final String transactionId = generateTransactionId(); + final String referenceNumber = journalEntryCommand.getReferenceNumber(); + final boolean manualEntry = true; - saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getDebits(), transactionId, - JournalEntryType.DEBIT); + if (isPredefinedRuleEntry) { + + final Long accountingRuleId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.ACCOUNTING_RULE.getValue()); + final BigDecimal amount = command.bigDecimalValueOfParameterNamed(JournalEntryJsonInputParams.AMOUNT.getValue()); + final AccountingRule accountingRule = this.accountingRuleRepository.findOne(accountingRuleId); + if (accountingRule == null) { throw new AccountingRuleNotFoundException(accountingRuleId); } + final GLAccount creditAccountHead = accountingRule.getAccountToCredit(); + validateGLAccountForTransaction(creditAccountHead); + final GLAccount debitAccountHead = accountingRule.getAccountToDebit(); + validateGLAccountForTransaction(debitAccountHead); + String description = accountingRule.getDescription(); + if (StringUtils.isNotBlank(journalEntryCommand.getComments())) { + description = journalEntryCommand.getComments(); + } + + JournalEntry creditEntry = JournalEntry.createNew(office, creditAccountHead, transactionId, manualEntry, transactionDate, + JournalEntryType.CREDIT, amount, description, null, null, referenceNumber); + this.glJournalEntryRepository.saveAndFlush(creditEntry); + + JournalEntry debitEntry = JournalEntry.createNew(office, debitAccountHead, transactionId, manualEntry, transactionDate, + JournalEntryType.DEBIT, amount, description, null, null, referenceNumber); + this.glJournalEntryRepository.saveAndFlush(debitEntry); + + + } else { - saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getCredits(), transactionId, - JournalEntryType.CREDIT); + saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getDebits(), transactionId, + JournalEntryType.DEBIT, referenceNumber); + + saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getCredits(), transactionId, + JournalEntryType.CREDIT, referenceNumber); + + } return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId) .withTransactionId(transactionId).build(); @@ -103,6 +140,19 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa } } + private void validateGLAccountForTransaction(GLAccount creditOrDebitAccountHead) { + /*** + * validate that the account allows manual adjustments and is not + * disabled + **/ + if (creditOrDebitAccountHead.isDisabled()) { + throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_DISABLED, null, creditOrDebitAccountHead.getName(), + creditOrDebitAccountHead.getGlCode()); + } else if (!creditOrDebitAccountHead.isManualEntriesAllowed()) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED, null, creditOrDebitAccountHead.getName(), + creditOrDebitAccountHead.getGlCode()); } + } + @Transactional @Override public CommandProcessingResult revertJournalEntry(final JsonCommand command) { @@ -168,56 +218,48 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException( GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } } - /*** check if credits and debits are valid **/ - final SingleDebitOrCreditEntryCommand[] credits = command.getCredits(); - final SingleDebitOrCreditEntryCommand[] debits = command.getDebits(); - - // atleast one debit or credit must be present - if ((credits == null || credits.length <= 0) || (debits == null || debits.length <= 0)) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); } - - // sum of all debits must be = sum of all credits - BigDecimal creditsSum = BigDecimal.ZERO; - BigDecimal debitsSum = BigDecimal.ZERO; - for (final SingleDebitOrCreditEntryCommand creditEntryCommand : credits) { - if (creditEntryCommand.getAmount() == null || creditEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); } - creditsSum = creditsSum.add(creditEntryCommand.getAmount()); + if(!command.getIsPredefinedRuleEntry()) { + /*** check if credits and debits are valid **/ + final SingleDebitOrCreditEntryCommand[] credits = command.getCredits(); + final SingleDebitOrCreditEntryCommand[] debits = command.getDebits(); + + // atleast one debit or credit must be present + if ((credits == null || credits.length <= 0) || (debits == null || debits.length <= 0)) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); } + + // sum of all debits must be = sum of all credits + BigDecimal creditsSum = BigDecimal.ZERO; + BigDecimal debitsSum = BigDecimal.ZERO; + for (final SingleDebitOrCreditEntryCommand creditEntryCommand : credits) { + if (creditEntryCommand.getAmount() == null || creditEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); } + creditsSum = creditsSum.add(creditEntryCommand.getAmount()); + } + for (final SingleDebitOrCreditEntryCommand debitEntryCommand : debits) { + if (debitEntryCommand.getAmount() == null || debitEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); } + debitsSum = debitsSum.add(debitEntryCommand.getAmount()); + } + if (creditsSum.compareTo(debitsSum) != 0) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_SUM_MISMATCH, null, null, null); } } - for (final SingleDebitOrCreditEntryCommand debitEntryCommand : debits) { - if (debitEntryCommand.getAmount() == null || debitEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); } - debitsSum = debitsSum.add(debitEntryCommand.getAmount()); - } - if (creditsSum.compareTo(debitsSum) != 0) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_SUM_MISMATCH, null, null, null); } } private void saveAllDebitOrCreditEntries(final JournalEntryCommand command, final Office office, final Date transactionDate, final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, final String transactionId, - final JournalEntryType type) { + final JournalEntryType type, final String referenceNumber) { final boolean manualEntry = true; for (final SingleDebitOrCreditEntryCommand singleDebitOrCreditEntryCommand : singleDebitOrCreditEntryCommands) { final GLAccount glAccount = this.glAccountRepository.findOne(singleDebitOrCreditEntryCommand.getGlAccountId()); if (glAccount == null) { throw new GLAccountNotFoundException(singleDebitOrCreditEntryCommand.getGlAccountId()); } - /*** - * validate that the account allows manual adjustments and is not - * disabled - **/ - if (glAccount.isDisabled()) { - throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_DISABLED, null, glAccount.getName(), - glAccount.getGlCode()); - } else if (!glAccount.isManualEntriesAllowed()) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED, null, glAccount.getName(), - glAccount.getGlCode()); } + validateGLAccountForTransaction(glAccount); String comments = command.getComments(); if (!StringUtils.isBlank(singleDebitOrCreditEntryCommand.getComments())) { comments = singleDebitOrCreditEntryCommand.getComments(); } - String referenceNumber = command.getReferenceNumber(); final JournalEntry glJournalEntry = JournalEntry.createNew(office, glAccount, transactionId, manualEntry, transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, referenceNumber); this.glJournalEntryRepository.saveAndFlush(glJournalEntry); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/rule/domain/AccountingRule.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/rule/domain/AccountingRule.java index e93e62fa5..f934efe68 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/rule/domain/AccountingRule.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/rule/domain/AccountingRule.java @@ -147,4 +147,8 @@ public class AccountingRule extends AbstractPersistable { this.accountToCredit = accountToCredit; } + public String getDescription() { + return this.description; + } + } \ No newline at end of file