diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/data/GLJournalEntryData.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/data/GLJournalEntryData.java index efdc4d717..4720c4b5e 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/data/GLJournalEntryData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/data/GLJournalEntryData.java @@ -24,6 +24,8 @@ public class GLJournalEntryData { @SuppressWarnings("unused") private final Long glAccountId; @SuppressWarnings("unused") + private final String glAccountCode; + @SuppressWarnings("unused") private final EnumOptionData glAccountClassification; @SuppressWarnings("unused") private final LocalDate entryDate; @@ -50,7 +52,7 @@ public class GLJournalEntryData { @SuppressWarnings("unused") private final Boolean reversed; - public GLJournalEntryData(Long id, Long officeId, String officeName, String glAccountName, Long glAccountId, + public GLJournalEntryData(Long id, Long officeId, String officeName, String glAccountName, Long glAccountId, String glAccountCode, EnumOptionData glAccountClassification, LocalDate entryDate, EnumOptionData entryType, BigDecimal amount, String transactionId, Boolean portfolioGenerated, String entityType, Long entityId, Long createdByUserId, LocalDate createdDate, String createdByUserName, String comments, Boolean reversed) { @@ -59,6 +61,7 @@ public class GLJournalEntryData { this.officeName = officeName; this.glAccountName = glAccountName; this.glAccountId = glAccountId; + this.glAccountCode = glAccountCode; this.glAccountClassification = glAccountClassification; this.entryDate = entryDate; this.entryType = entryType; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/infrastructure/impl/GoogleGsonAccountingApiJsonSerializerService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/infrastructure/impl/GoogleGsonAccountingApiJsonSerializerService.java index 2ec1ba077..8096115e3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/infrastructure/impl/GoogleGsonAccountingApiJsonSerializerService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/infrastructure/impl/GoogleGsonAccountingApiJsonSerializerService.java @@ -35,8 +35,9 @@ public class GoogleGsonAccountingApiJsonSerializerService implements AccountingA "lastUpdatedByUsername", "comments", "allowedOffices")); private static final Set GL_JOURNAL_ENTRY_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "officeId", "officeName", - "glAccountName", "glAccountId", "glAccountClassification", "entryDate", "entryType", "amount", "transactionId", - "portfolioGenerated", "entityType", "entityId", "createdByUserId", "createdDate", "createdByUserName", "comments", "reversed")); + "glAccountName", "glAccountId", "glAccountCode", "glAccountClassification", "entryDate", "entryType", "amount", + "transactionId", "portfolioGenerated", "entityType", "entityId", "createdByUserId", "createdDate", "createdByUserName", + "comments", "reversed")); private final GoogleGsonSerializerHelper helper; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/GLAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/GLAccount.java index cc2af5bd1..013afaa37 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/GLAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/GLAccount.java @@ -42,7 +42,6 @@ public class GLAccount extends AbstractPersistable { @Column(name = "manual_journal_entries_allowed", nullable = false) private boolean manualEntriesAllowed = true; - @SuppressWarnings("unused") @Column(name = "classification_enum", nullable = false) private Integer classification; @@ -133,4 +132,12 @@ public class GLAccount extends AbstractPersistable { return this.name; } + public Integer getClassification() { + return this.classification; + } + + public void setClassification(Integer classification) { + this.classification = classification; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/exceptions/ProductToGLAccountMappingInvalidException.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/exceptions/ProductToGLAccountMappingInvalidException.java new file mode 100755 index 000000000..6b71d85f5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/exceptions/ProductToGLAccountMappingInvalidException.java @@ -0,0 +1,17 @@ +package org.mifosplatform.accounting.exceptions; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * A {@link RuntimeException} thrown when product to GL account mapping are not + * found. + */ +public class ProductToGLAccountMappingInvalidException extends AbstractPlatformDomainRuleException { + + public ProductToGLAccountMappingInvalidException(final String paramName, String accountName, Long accountId, + String actualAccountCategory, String expectedAccountCategory) { + super("error.msg." + paramName + ".invalid.account.type", "Passed in GLAccount " + paramName + " with Id " + accountId + + "maps to the account " + accountName + " of type " + actualAccountCategory + ", the expected account type was " + + expectedAccountCategory, paramName, accountId, accountName, actualAccountCategory, expectedAccountCategory); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java new file mode 100755 index 000000000..91f3d749e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -0,0 +1,135 @@ +package org.mifosplatform.accounting.serialization; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.commons.lang.StringUtils; +import org.mifosplatform.accounting.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.domain.GLAccount; +import org.mifosplatform.accounting.service.ProductToGLAccountMappingWritePlatformService; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; + +/** + * TODO Vishwas find a better approach for validation + * + * Currently, validation of the passed in JSON is done before calling save or + * update method on the target resource (in our case the loan Product) + * + * However, in the case of a loan product it would be difficult to validate the + * passed in JSON for valid {@link LoanProduct} to {@link GLAccount} mappings + * during update because of the following scenario + * + * The accounting rule type may be changed in the update command, so we would + * have to validate if all required account heads for a particular account type + * have been passed in (would be different for CASH and Accrual based). However, + * till we have access to the domain object it would not be possible to detect + * if an accounting rule has actually been changed + * + * Hence, method {@link #validateForCreate(String)} from this class is called + * separately for validation only if an accounting rule change is detected by + * {@link ProductToGLAccountMappingWritePlatformService} + * + * Also, the class is probably named wrong (*FromApiJsonDeserializer) should + * probably be named as (*Validator) instead + * + */ +@Component +public final class ProductToGLAccountMappingFromApiJsonDeserializer { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public ProductToGLAccountMappingFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForCreate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanproduct"); + + final JsonElement element = fromApiJsonHelper.parse(json); + + // accounting related data validation + final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingType", element, Locale.getDefault()); + baseDataValidator.reset().parameter("accountingType").value(accountingRuleType).notNull().inMinMaxRange(1, 3); + + if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) { + + final Long fundAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(fundAccountId).notNull() + .integerGreaterThanZero(); + + final Long loanPortfolioAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId) + .notNull().integerGreaterThanZero(); + + final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId) + .notNull().integerGreaterThanZero(); + + final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) + .notNull().integerGreaterThanZero(); + + final Long incomeFromPenaltyId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) + .notNull().integerGreaterThanZero(); + + final Long writeOffAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), + element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) + .notNull().integerGreaterThanZero(); + + } + + if (isAccrualBasedAccounting(accountingRuleType)) { + + final Long receivableInterestAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue()) + .value(receivableInterestAccountId).notNull().integerGreaterThanZero(); + + final Long receivableFeeAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId) + .notNull().integerGreaterThanZero(); + + final Long receivablePenaltyAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue()) + .value(receivablePenaltyAccountId).notNull().integerGreaterThanZero(); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private boolean isCashBasedAccounting(final Integer accountingRuleType) { + return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType); + } + + private boolean isAccrualBasedAccounting(final Integer accountingRuleType) { + return AccountingRuleType.ACCRUAL_BASED.getValue().equals(accountingRuleType); + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingWritePlatformService.java index 9730ec9dc..366492a26 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingWritePlatformService.java @@ -8,7 +8,8 @@ public interface ProductToGLAccountMappingWritePlatformService { void createLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command); - Map updateLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command, boolean accountingRuleChanged); + Map updateLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command, boolean accountingRuleChanged, + int accountingRuleTypeId); void deleteLoanProductToGLAccountMapping(Long loanProductId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLJournalEntryReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLJournalEntryReadPlatformServiceImpl.java index 8f4fbd26c..4f3273984 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLJournalEntryReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLJournalEntryReadPlatformServiceImpl.java @@ -37,7 +37,7 @@ public class GLJournalEntryReadPlatformServiceImpl implements GLJournalEntryRead public String schema() { return " journalEntry.id as journalEntryId, glAccount.classification_enum as classification ," - + " glAccount.name as glAccountName, journalEntry.account_id as glAccountId," + + " glAccount.name as glAccountName, glAccount.gl_code as glCode, journalEntry.account_id as glAccountId," + " journalEntry.office_id as officeId, office.name as officeName, " + " journalEntry.portfolio_generated as portfolioGenerated,journalEntry.entry_date as entryDate, " + " journalEntry.type_enum as entryType,journalEntry.amount as amount, journalEntry.transaction_id as transactionId," @@ -55,6 +55,7 @@ public class GLJournalEntryReadPlatformServiceImpl implements GLJournalEntryRead Long journalEntryId = rs.getLong("journalEntryId"); Long officeId = rs.getLong("officeId"); String officeName = rs.getString("officeName"); + String glCode = rs.getString("glCode"); String glAccountName = rs.getString("glAccountName"); Long glAccountId = rs.getLong("glAccountId"); final int accountTypeId = JdbcSupport.getInteger(rs, "classification"); @@ -73,7 +74,7 @@ public class GLJournalEntryReadPlatformServiceImpl implements GLJournalEntryRead String comments = rs.getString("comments"); Boolean reversed = rs.getBoolean("reversed"); - return new GLJournalEntryData(journalEntryId, officeId, officeName, glAccountName, glAccountId, accountType, entryDate, + return new GLJournalEntryData(journalEntryId, officeId, officeName, glAccountName, glAccountId, glCode, accountType, entryDate, entryType, amount, transactionId, portfolioGenerated, entityType, entityId, createdByUserId, createdDate, createdByUserName, comments, reversed); } @@ -133,7 +134,7 @@ public class GLJournalEntryReadPlatformServiceImpl implements GLJournalEntryRead } } - sql += " order by journalEntry.entry_date"; + sql += " order by journalEntry.entry_date,journalEntry.transaction_id"; Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos); return this.jdbcTemplate.query(sql, rm, finalObjectArray); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingWritePlatformServiceImpl.java index e67cda69e..8ec074334 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -10,11 +10,14 @@ import org.mifosplatform.accounting.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; import org.mifosplatform.accounting.domain.GLAccount; import org.mifosplatform.accounting.domain.GLAccountRepository; +import org.mifosplatform.accounting.domain.GLAccountType; import org.mifosplatform.accounting.domain.PortfolioProductType; import org.mifosplatform.accounting.domain.ProductToGLAccountMapping; import org.mifosplatform.accounting.domain.ProductToGLAccountMappingRepository; import org.mifosplatform.accounting.exceptions.GLAccountNotFoundException; +import org.mifosplatform.accounting.exceptions.ProductToGLAccountMappingInvalidException; import org.mifosplatform.accounting.exceptions.ProductToGLAccountMappingNotFoundException; +import org.mifosplatform.accounting.serialization.ProductToGLAccountMappingFromApiJsonDeserializer; import org.mifosplatform.accounting.service.ProductToGLAccountMappingWritePlatformService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; @@ -31,75 +34,72 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc private final GLAccountRepository accountRepository; private final ProductToGLAccountMappingRepository accountMappingRepository; private final FromJsonHelper fromApiJsonHelper; + private final ProductToGLAccountMappingFromApiJsonDeserializer deserializer; @Autowired public ProductToGLAccountMappingWritePlatformServiceImpl(final GLAccountRepository glAccountRepository, - final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper) { + final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper, + final ProductToGLAccountMappingFromApiJsonDeserializer deserializer) { this.accountRepository = glAccountRepository; this.accountMappingRepository = glAccountMappingRepository; this.fromApiJsonHelper = fromApiJsonHelper; + this.deserializer = deserializer; } @Override @Transactional public void createLoanProductToGLAccountMapping(final Long loanProductId, final JsonCommand command) { - final JsonElement element = fromApiJsonHelper.parse(command.json()); - final Integer accountingRuleTypeId = fromApiJsonHelper.extractIntegerNamed("accountingType", element, Locale.getDefault()); + final JsonElement element = this.fromApiJsonHelper.parse(command.json()); + final Integer accountingRuleTypeId = this.fromApiJsonHelper.extractIntegerNamed("accountingType", element, Locale.getDefault()); final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId); - final Long loanPortfolioAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), - element); - final Long fundAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); - final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), - element); - final Long incomeFromFeeId = fromApiJsonHelper - .extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element); - final Long incomeFromPenaltyId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); - final Long writeOffAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), - element); - switch (accountingRuleType) { case NONE: break; case CASH_BASED: // asset - saveLoanToAccountMapping(fundAccountId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()); - saveLoanToAccountMapping(loanPortfolioAccountId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); - saveLoanToAccountMapping(incomeFromInterestId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue()); + saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()); + saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); // income - saveLoanToAccountMapping(incomeFromFeeId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue()); - saveLoanToAccountMapping(incomeFromPenaltyId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); + saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue()); + saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue()); + saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); // expenses - saveLoanToAccountMapping(writeOffAccountId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue()); + saveLoanToExpenseAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue()); break; case ACCRUAL_BASED: - final Long receivableInterestAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); - final Long receivableFeeAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element); - final Long receivablePenaltyAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); // assets (including receivables) - saveLoanToAccountMapping(fundAccountId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()); - saveLoanToAccountMapping(loanPortfolioAccountId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); - saveLoanToAccountMapping(incomeFromInterestId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue()); - - saveLoanToAccountMapping(receivableInterestAccountId, loanProductId, + saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()); + saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); + saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue()); - saveLoanToAccountMapping(receivableFeeAccountId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue()); - saveLoanToAccountMapping(receivablePenaltyAccountId, loanProductId, + saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue()); + saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue()); // income - saveLoanToAccountMapping(incomeFromFeeId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue()); - saveLoanToAccountMapping(incomeFromPenaltyId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); + saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue()); + saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue()); + saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); // expenses - saveLoanToAccountMapping(writeOffAccountId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue()); + saveLoanToExpenseAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue()); break; } } @@ -107,162 +107,322 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc @Override @Transactional public Map updateLoanProductToGLAccountMapping(final Long loanProductId, final JsonCommand command, - boolean accountingRuleChanged) { + final boolean accountingRuleChanged, int accountingRuleTypeId) { + /*** + * Variable tracks all accounting mapping properties that have been + * updated + ***/ final Map changes = new HashMap(); - - final JsonElement element = fromApiJsonHelper.parse(command.json()); - final Integer accountingRuleTypeId = fromApiJsonHelper.extractIntegerNamed("accountingType", element, Locale.getDefault()); + final JsonElement element = this.fromApiJsonHelper.parse(command.json()); final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId); - final Long fundAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); - final Long loanPortfolioAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), - element); - final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), - element); - final Long incomeFromFeeId = fromApiJsonHelper - .extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element); - final Long incomeFromPenaltyId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); - final Long writeOffAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), - element); - - final Long receivableInterestAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); - final Long receivableFeeAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), - element); - final Long receivablePenaltyAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); - + /*** + * If the accounting rule has been changed, delete all existing mapping + * for the product and recreate a new set of mappings + ***/ if (accountingRuleChanged) { - deleteLoanProductToGLAccountMapping(loanProductId); - createLoanProductToGLAccountMapping(loanProductId, command); - - switch (accountingRuleType) { - case NONE: - break; - case CASH_BASED: - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); - break; - case ACCRUAL_BASED: - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), receivableFeeAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId); - break; - } - } else { - switch (accountingRuleType) { - case NONE: - break; - case CASH_BASED: - // asset - updateLoanToAccountMapping(fundAccountId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), - CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), changes); - updateLoanToAccountMapping(loanPortfolioAccountId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), - CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), - changes); - updateLoanToAccountMapping(incomeFromInterestId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), - CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), changes); - - // income - updateLoanToAccountMapping(incomeFromFeeId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), - CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), - changes); - updateLoanToAccountMapping(incomeFromPenaltyId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), - CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), changes); - - // expenses - updateLoanToAccountMapping(writeOffAccountId, loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), - CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), changes); - break; - case ACCRUAL_BASED: - // assets (including receivables) - updateLoanToAccountMapping(fundAccountId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), - changes); - updateLoanToAccountMapping(loanPortfolioAccountId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), - changes); - updateLoanToAccountMapping(incomeFromInterestId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), changes); - - updateLoanToAccountMapping(receivableInterestAccountId, loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), changes); - - updateLoanToAccountMapping(receivableFeeAccountId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), changes); - - updateLoanToAccountMapping(receivablePenaltyAccountId, loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), changes); - - // income - updateLoanToAccountMapping(incomeFromFeeId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), changes); - updateLoanToAccountMapping(incomeFromPenaltyId, loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), changes); - - // expenses - updateLoanToAccountMapping(writeOffAccountId, loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), changes); - break; - } + deserializer.validateForCreate(command.json()); + handleChangesToAccountingRuleType(loanProductId, command, changes, element, accountingRuleType); + }/*** else examine and update individual changes ***/ + else { + handleChangesToLoanProductToGLAccountMappings(loanProductId, changes, element, accountingRuleType); } return changes; } - private void saveLoanToAccountMapping(final Long glAccountId, final Long productId, final int accountTypeId) { - GLAccount glAccount = accountRepository.findOne(glAccountId); - if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); } - ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, - PortfolioProductType.LOAN.getValue(), accountTypeId); - accountMappingRepository.save(accountMapping); - } - - private void updateLoanToAccountMapping(final Long glAccountId, final Long productId, final int accountTypeId, - final String accountTypeName, final String accountParamValue, final Map changes) { - // get the existing product - ProductToGLAccountMapping accountMapping = accountMappingRepository.findByProductIdAndProductTypeAndFinancialAccountType(productId, - PortfolioProductType.LOAN.getValue(), accountTypeId); - if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.LOAN, productId, - accountTypeName); } - if (accountMapping.getGlAccount().getId() != glAccountId) { - GLAccount glAccount = accountRepository.findOne(glAccountId); - if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); } - changes.put(accountParamValue, glAccountId); - accountMapping.setGlAccount(glAccount); - accountMappingRepository.save(accountMapping); - } - } - @Override public void deleteLoanProductToGLAccountMapping(final Long loanProductId) { - List productToGLAccountMappings = accountMappingRepository.findByProductIdAndProductType(loanProductId, - PortfolioProductType.LOAN.getValue()); + final List productToGLAccountMappings = this.accountMappingRepository.findByProductIdAndProductType( + loanProductId, PortfolioProductType.LOAN.getValue()); if (productToGLAccountMappings != null && productToGLAccountMappings.size() > 0) { - accountMappingRepository.deleteInBatch(productToGLAccountMappings); + this.accountMappingRepository.deleteInBatch(productToGLAccountMappings); } } + + /** + * Examines and updates each account mapping for given loan product with + * changes passed in from the Json element + * + * @param loanProductId + * @param changes + * @param element + * @param accountingRuleType + */ + private void handleChangesToLoanProductToGLAccountMappings(final Long loanProductId, final Map changes, + final JsonElement element, final AccountingRuleType accountingRuleType) { + switch (accountingRuleType) { + case NONE: + break; + case CASH_BASED: + // asset + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes); + + // income + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), changes); + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), changes); + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), + loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), + CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes); + + // expenses + mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), + loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), + CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes); + break; + case ACCRUAL_BASED: + // assets (including receivables) + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), + loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), + ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), + loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(), + ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.toString(), changes); + + // income + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), + changes); + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), + changes); + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), + loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes); + + // expenses + mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), + loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), + ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes); + break; + } + } + + /** + * delete all existing mapping for a product and recreates a new set of + * mappings (to be used exclusively when the selected accounting rule has + * changed) + * + * @param loanProductId + * @param command + * @param changes + * @param element + * @param accountingRuleType + */ + private void handleChangesToAccountingRuleType(final Long loanProductId, final JsonCommand command, final Map changes, + final JsonElement element, final AccountingRuleType accountingRuleType) { + deleteLoanProductToGLAccountMapping(loanProductId); + createLoanProductToGLAccountMapping(loanProductId, command); + final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); + final Long loanPortfolioAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element); + final Long incomeFromInterestId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); + final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + element); + final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); + + final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); + final Long receivableFeeAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element); + final Long receivablePenaltyAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); + + switch (accountingRuleType) { + case NONE: + break; + case CASH_BASED: + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); + break; + case ACCRUAL_BASED: + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), receivableFeeAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId); + break; + } + } + + /** + * Utility wrapper for + * {@link #saveLoanToAccountMapping(JsonElement, String, Long, int, GLAccountType)} + * + * @param element + * @param paramName + * @param productId + * @param placeHolderTypeId + */ + private void saveLoanToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveLoanToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET); + } + + /** + * Utility wrapper for + * {@link #saveLoanToAccountMapping(JsonElement, String, Long, int, GLAccountType)} + * + * @param element + * @param paramName + * @param productId + * @param placeHolderTypeId + */ + private void saveLoanToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveLoanToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME); + } + + /** + * Utility wrapper for + * {@link #saveLoanToAccountMapping(JsonElement, String, Long, int, GLAccountType)} + * + * @param element + * @param paramName + * @param productId + * @param placeHolderTypeId + */ + private void saveLoanToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveLoanToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE); + } + + /** + * @param element + * @param paramName + * @param productId + * @param placeHolderTypeId + * @param expectedAccountType + */ + private void saveLoanToAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId, final GLAccountType expectedAccountType) { + final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element); + final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); + + final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, + PortfolioProductType.LOAN.getValue(), placeHolderTypeId); + this.accountMappingRepository.save(accountMapping); + } + + /** + * Utility wrapper for + * {@link #mergeLoanToAccountMappingChanges(JsonElement, String, Long, int, String, Map, GLAccountType)} + * + * @param element + * @param paramName + * @param productId + * @param accountTypeId + * @param accountTypeName + * @param changes + */ + private void mergeLoanToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeLoanToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET); + } + + /** + * Utility wrapper for + * {@link #mergeLoanToAccountMappingChanges(JsonElement, String, Long, int, String, Map, GLAccountType)} + * + * @param element + * @param paramName + * @param productId + * @param accountTypeId + * @param accountTypeName + * @param changes + */ + private void mergeLoanToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeLoanToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME); + } + + /** + * Utility wrapper for + * {@link #mergeLoanToAccountMappingChanges(JsonElement, String, Long, int, String, Map, GLAccountType)} + * + * @param element + * @param paramName + * @param productId + * @param accountTypeId + * @param accountTypeName + * @param changes + */ + private void mergeLoanToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeLoanToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE); + } + + /** + * Method merges changes passed in through JSON element + * + * @param element + * @param paramName + * @param productId + * @param accountTypeId + * @param accountTypeName + * @param changes + * @param expectedAccountType + */ + private void mergeLoanToAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes, + final GLAccountType expectedAccountType) { + final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element); + + // get the existing product + if (accountId != null) { + final ProductToGLAccountMapping accountMapping = this.accountMappingRepository + .findByProductIdAndProductTypeAndFinancialAccountType(productId, PortfolioProductType.LOAN.getValue(), accountTypeId); + if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.LOAN, productId, + accountTypeName); } + if (accountMapping.getGlAccount().getId() != accountId) { + final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); + changes.put(paramName, accountId); + accountMapping.setGlAccount(glAccount); + this.accountMappingRepository.save(accountMapping); + } + } + } + + /** + * Fetches account with a particular Id and throws and Exception it is not + * of the expected Account Category ('DEBIT','CREDIT' etc) + * + * @param paramName + * @param expectedAccountType + * @param accountId + * @return + */ + private GLAccount getAccountByIdAndType(final String paramName, final GLAccountType expectedAccountType, final Long accountId) { + final GLAccount glAccount = this.accountRepository.findOne(accountId); + if (glAccount == null) { throw new GLAccountNotFoundException(accountId); } + + // validate account is of the expected Type + if (glAccount.getClassification().intValue() != expectedAccountType.getValue()) { throw new ProductToGLAccountMappingInvalidException( + paramName, glAccount.getName(), accountId, GLAccountType.fromInt(glAccount.getClassification()).toString(), + expectedAccountType.toString()); } + return glAccount; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java index 0e54b846d..798eb51b3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -155,7 +155,7 @@ public class LoanProductsApiResource { Map accountingMappings = null; if (loanProduct.hasAccountingEnabled()) { - accountingMappings = accountMappingReadPlatformService.fetchAccountMappingDetailsForLoanProduct(productId, loanProduct.accountingRuleType()); + accountingMappings = accountMappingReadPlatformService.fetchAccountMappingDetailsForLoanProduct(productId, loanProduct.accountingRuleType().getId().intValue()); loanProduct = LoanProductData.withAccountingMappings(loanProduct, accountingMappings); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index 322c80301..d586d2f95 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -11,6 +11,7 @@ import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.fund.data.FundData; +import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -44,8 +45,7 @@ public class LoanProductData { private final EnumOptionData interestCalculationPeriodType; private final BigDecimal inArrearsTolerance; - // FIXME - KW - This should be returned for serialization as an EnumOptionData like the other Enumerated values - private final Integer accountingType; + private final EnumOptionData accountingType; private final Collection charges; @@ -94,7 +94,7 @@ public class LoanProductData { final Long transactionProcessingStrategyId = null; final String transactionProcessingStrategyName = null; final Collection charges = null; - final Integer accountingType = null; + final EnumOptionData accountingType = null; return new LoanProductData(id, name, description, currency, principal, tolerance, numberOfRepayments, loanTermFrequency, repaymentEvery, interestRatePerPeriod, annualInterestRate, loanTermFrequencyType, repaymentFrequencyType, @@ -126,7 +126,7 @@ public class LoanProductData { final Long transactionProcessingStrategyId = null; final String transactionProcessingStrategyName = null; final Collection charges = null; - final Integer accountingType = 1; + final EnumOptionData accountingType = LoanEnumerations.accountingRuleType(AccountingRuleType.NONE); return new LoanProductData(id, name, description, currency, principal, tolerance, numberOfRepayments, loanTermFrequency, repaymentEvery, interestRatePerPeriod, annualInterestRate, loanTermFrequencyType, repaymentFrequencyType, @@ -150,7 +150,7 @@ public class LoanProductData { final EnumOptionData interestRateFrequencyType, final EnumOptionData amortizationType, final EnumOptionData interestType, final EnumOptionData interestCalculationPeriodType, final Long fundId, final String fundName, final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName, - final Collection charges, final Integer accountingType) { + final Collection charges, final EnumOptionData accountingType) { this.id = id; this.name = name; this.description = description; @@ -261,12 +261,12 @@ public class LoanProductData { return chargesLocal; } - public Integer accountingRuleType() { + public EnumOptionData accountingRuleType() { return this.accountingType; } public boolean hasAccountingEnabled() { - return this.accountingType > 1; + return this.accountingType.getId() > 1; } public Long getId() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java index 3490b58f6..d69beba0a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java @@ -5,12 +5,14 @@ import java.util.Map; public enum AccountingRuleType { - NONE(1), CASH_BASED(2), ACCRUAL_BASED(3); + NONE(1, "accountingRuleType.none"), CASH_BASED(2, "accountingRuleType.cash"), ACCRUAL_BASED(3, "accountingRuleType.accrual"); private final Integer value; + private final String code; - private AccountingRuleType(final Integer value) { + private AccountingRuleType(final Integer value, String code) { this.value = value; + this.code = code; } @Override @@ -22,6 +24,10 @@ public enum AccountingRuleType { return this.value; } + public String getCode() { + return this.code; + } + private static final Map intToEnumMap = new HashMap(); static { for (AccountingRuleType type : AccountingRuleType.values()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java index 2311f1e4d..1857a09ef 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java @@ -117,7 +117,7 @@ public final class LoanProductCommandFromApiJsonDeserializer { final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingType", element, Locale.getDefault()); baseDataValidator.reset().parameter("accountingType").value(accountingRuleType).notNull().inMinMaxRange(1, 3); - if (isCashBased(accountingRuleType) || isAccrualBased(accountingRuleType)) { + if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) { final Long fundAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(fundAccountId).notNull() @@ -127,27 +127,7 @@ public final class LoanProductCommandFromApiJsonDeserializer { LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element); baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId) .notNull().integerGreaterThanZero(); - } - if (isAccrualBased(accountingRuleType)) { - - final Long receivableInterestAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue()) - .value(receivableInterestAccountId).notNull().integerGreaterThanZero(); - - final Long receivableFeeAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId) - .notNull().integerGreaterThanZero(); - - final Long receivablePenaltyAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue()) - .value(receivablePenaltyAccountId).notNull().integerGreaterThanZero(); - } - - if (isCashBased(accountingRuleType) || isAccrualBased(accountingRuleType)) { final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed( LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId) @@ -167,6 +147,25 @@ public final class LoanProductCommandFromApiJsonDeserializer { element); baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) .notNull().integerGreaterThanZero(); + + } + + if (isAccrualBasedAccounting(accountingRuleType)) { + + final Long receivableInterestAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue()) + .value(receivableInterestAccountId).notNull().integerGreaterThanZero(); + + final Long receivableFeeAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId) + .notNull().integerGreaterThanZero(); + + final Long receivablePenaltyAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue()) + .value(receivablePenaltyAccountId).notNull().integerGreaterThanZero(); } throwExceptionIfValidationWarningsExist(dataValidationErrors); @@ -267,72 +266,61 @@ public final class LoanProductCommandFromApiJsonDeserializer { .inMinMaxRange(0, 1); } - // FIXME - KW - accounting here doesnt allow me to just update one part - // of cash based or accrual based settings? - // accounting related data validation final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingType", element, Locale.getDefault()); - baseDataValidator.reset().parameter("accountingType").value(accountingRuleType).notNull().inMinMaxRange(1, 3); + baseDataValidator.reset().parameter("accountingType").value(accountingRuleType).ignoreIfNull().inMinMaxRange(1, 3); - if (isCashBased(accountingRuleType) || isAccrualBased(accountingRuleType)) { + final Long fundAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(fundAccountId).ignoreIfNull() + .integerGreaterThanZero(); - final Long fundAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(fundAccountId).notNull() - .integerGreaterThanZero(); + final Long loanPortfolioAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), + element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId) + .ignoreIfNull().integerGreaterThanZero(); - final Long loanPortfolioAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId) - .notNull().integerGreaterThanZero(); - } + final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), + element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId) + .ignoreIfNull().integerGreaterThanZero(); - if (isAccrualBased(accountingRuleType)) { + final Long incomeFromFeeId = fromApiJsonHelper + .extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) + .ignoreIfNull().integerGreaterThanZero(); - final Long receivableInterestAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue()) - .value(receivableInterestAccountId).notNull().integerGreaterThanZero(); + final Long incomeFromPenaltyId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) + .ignoreIfNull().integerGreaterThanZero(); - final Long receivableFeeAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId) - .notNull().integerGreaterThanZero(); + final Long writeOffAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), + element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) + .ignoreIfNull().integerGreaterThanZero(); - final Long receivablePenaltyAccountId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue()) - .value(receivablePenaltyAccountId).notNull().integerGreaterThanZero(); - } + final Long receivableInterestAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue()) + .value(receivableInterestAccountId).ignoreIfNull().integerGreaterThanZero(); - if (isCashBased(accountingRuleType) || isAccrualBased(accountingRuleType)) { - final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId) - .notNull().integerGreaterThanZero(); + final Long receivableFeeAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), + element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId) + .ignoreIfNull().integerGreaterThanZero(); - final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), - element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) - .notNull().integerGreaterThanZero(); - - final Long incomeFromPenaltyId = fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) - .notNull().integerGreaterThanZero(); - - final Long writeOffAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), - element); - baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) - .notNull().integerGreaterThanZero(); - } + final Long receivablePenaltyAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue()) + .value(receivablePenaltyAccountId).ignoreIfNull().integerGreaterThanZero(); throwExceptionIfValidationWarningsExist(dataValidationErrors); } - private boolean isCashBased(final Integer accountingRuleType) { + private boolean isCashBasedAccounting(final Integer accountingRuleType) { return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType); } - private boolean isAccrualBased(final Integer accountingRuleType) { + private boolean isAccrualBasedAccounting(final Integer accountingRuleType) { return AccountingRuleType.ACCRUAL_BASED.getValue().equals(accountingRuleType); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index 2e3818200..799085f5c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -3,6 +3,7 @@ package org.mifosplatform.portfolio.loanproduct.service; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; +import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -248,4 +249,13 @@ public class LoanEnumerations { return optionData; } + + public static EnumOptionData accountingRuleType(final int id) { + return accountingRuleType(AccountingRuleType.fromInt(id)); + } + + public static EnumOptionData accountingRuleType(final AccountingRuleType type) { + EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), type.toString()); + return optionData; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index d43e3d4f3..e7ef0ec75 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -129,7 +129,10 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final Integer numberOfRepayments = JdbcSupport.getInteger(rs, "numberOfRepayments"); final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaidEvery"); - final Integer accountingType = JdbcSupport.getInteger(rs, "accountingType"); + + final Integer accountingTypeId = JdbcSupport.getInteger(rs, "accountingType"); + final EnumOptionData accountingRuleType = LoanEnumerations.accountingRuleType(accountingTypeId); + final Integer loanTermFrequency = repaymentEvery * numberOfRepayments; final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod"); final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); @@ -155,7 +158,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo return new LoanProductData(id, name, description, currency, principal, tolerance, numberOfRepayments, loanTermFrequency, repaymentEvery, interestRatePerPeriod, annualInterestRate, loanTermFrequencyType, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, - transactionStrategyId, transactionStrategyName, this.charges, accountingType); + transactionStrategyId, transactionStrategyName, this.charges, accountingRuleType); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index c798efc5f..6d628aca9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -154,12 +154,12 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro final Set charges = this.assembleSetOfCharges(command, product.getCurrency().getCode()); product.update(charges); } - + // accounting related changes boolean accountingTypeChanged = changes.containsKey("accountingType"); - if (accountingTypeChanged) { - accountMappingWritePlatformService.updateLoanProductToGLAccountMapping(product.getId(), command, accountingTypeChanged); - } + final Map accountingMappingChanges = accountMappingWritePlatformService.updateLoanProductToGLAccountMapping( + product.getId(), command, accountingTypeChanged, product.getAccountingType()); + changes.putAll(accountingMappingChanges); if (!changes.isEmpty()) { this.loanProductRepository.saveAndFlush(product);