FINERACT-1926: External owner accounting

This commit is contained in:
Adam Saghy
2023-06-12 11:24:31 +02:00
committed by Arnold Gálovics
parent 437b8e9cae
commit 582f36ad34
416 changed files with 1952 additions and 613 deletions
@@ -0,0 +1,73 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
@Entity
@Table(name = "acc_product_mapping", uniqueConstraints = { @UniqueConstraint(columnNames = { "product_id", "product_type",
"financial_account_type", "payment_type" }, name = "financial_action") })
public class ProductToGLAccountMapping extends AbstractPersistableCustom {
@ManyToOne(optional = true)
@JoinColumn(name = "gl_account_id")
private GLAccount glAccount;
@Column(name = "product_id", nullable = true)
private Long productId;
@ManyToOne
@JoinColumn(name = "payment_type", nullable = true)
private PaymentType paymentType;
@ManyToOne
@JoinColumn(name = "charge_id", nullable = true)
private Charge charge;
@Column(name = "product_type", nullable = true)
private int productType;
@Column(name = "financial_account_type", nullable = true)
private int financialAccountType;
public static ProductToGLAccountMapping createNew(final GLAccount glAccount, final Long productId, final int productType,
final int financialAccountType) {
return new ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId).setProductType(productType)
.setFinancialAccountType(financialAccountType);
}
}
@@ -0,0 +1,64 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ProductToGLAccountMappingRepository
extends JpaRepository<ProductToGLAccountMapping, Long>, JpaSpecificationExecutor<ProductToGLAccountMapping> {
ProductToGLAccountMapping findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(Long productId, int productType,
int financialAccountType, Long paymentType);
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId= :productId and mapping.productType= :productType and mapping.financialAccountType= :financialAccountType and mapping.charge.id= :chargeId")
ProductToGLAccountMapping findProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(@Param("productId") Long productId,
@Param("productType") int productType, @Param("financialAccountType") int financialAccountType,
@Param("chargeId") Long ChargeId);
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL")
ProductToGLAccountMapping findCoreProductToFinAccountMapping(@Param("productId") Long productId, @Param("productType") int productType,
@Param("financialAccountType") int financialAccountType);
/***
* The financial Account Type for a fund source will always be an asset (1)
***/
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=1 and mapping.paymentType is not NULL")
List<ProductToGLAccountMapping> findAllPaymentTypeToFundSourceMappings(@Param("productId") Long productId,
@Param("productType") int productType);
/***
* The financial Account Type for income from interest will always be 4
***/
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=4 and mapping.charge is not NULL")
List<ProductToGLAccountMapping> findAllFeeToIncomeAccountMappings(@Param("productId") Long productId,
@Param("productType") int productType);
/***
* The financial Account Type for income from interest will always be 5
***/
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=5 and mapping.charge is not NULL")
List<ProductToGLAccountMapping> findAllPenaltyToIncomeAccountMappings(@Param("productId") Long productId,
@Param("productType") int productType);
List<ProductToGLAccountMapping> findByProductIdAndProductType(Long productId, int productType);
}
@@ -0,0 +1,35 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.exception;
import org.apache.fineract.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, final String accountName, final Long accountId,
final String actualAccountCategory, final 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 one among" + expectedAccountCategory,
paramName, accountId, accountName, actualAccountCategory, expectedAccountCategory);
}
}
@@ -0,0 +1,33 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.exception;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when product to GL account mapping are not found.
*/
public class ProductToGLAccountMappingNotFoundException extends AbstractPlatformResourceNotFoundException {
public ProductToGLAccountMappingNotFoundException(final PortfolioProductType type, final Long productId, final String accountType) {
super("error.msg.productToAccountMapping.not.found", "Mapping for product of type " + type.toString() + " with Id " + productId
+ " does not exist for an account of type " + accountType, type.toString(), productId, accountType);
}
}
@@ -0,0 +1,292 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.serialization;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import com.google.gson.JsonElement;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.shareproducts.constants.ShareProductApiConstants;
import org.springframework.stereotype.Component;
/**
* 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 #validateForLoanProductCreate(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
@RequiredArgsConstructor
public final class ProductToGLAccountMappingFromApiJsonDeserializer {
private final FromJsonHelper fromApiJsonHelper;
public void validateForLoanProductCreate(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanproduct");
final JsonElement element = this.fromApiJsonHelper.parse(json);
// accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) {
final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.FUND_SOURCE.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.FUND_SOURCE.getValue()).value(fundAccountId).notNull()
.integerGreaterThanZero();
final Long loanPortfolioAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId)
.notNull().integerGreaterThanZero();
final Long incomeFromInterestId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId)
.notNull().integerGreaterThanZero();
final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_FEES.getValue(),
element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId).notNull()
.integerGreaterThanZero();
final Long incomeFromPenaltyId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
.notNull().integerGreaterThanZero();
final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue())
.value(incomeFromRecoveryAccountId).notNull().integerGreaterThanZero();
final Long writeOffAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
.notNull().integerGreaterThanZero();
final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.OVERPAYMENT.getValue(),
element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.OVERPAYMENT.getValue()).value(overpaymentAccountId).notNull()
.integerGreaterThanZero();
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue())
.value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
}
if (isAccrualBasedAccounting(accountingRuleType)) {
final Long receivableInterestAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue())
.value(receivableInterestAccountId).notNull().integerGreaterThanZero();
final Long receivableFeeAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.FEES_RECEIVABLE.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.FEES_RECEIVABLE.getValue()).value(receivableFeeAccountId)
.notNull().integerGreaterThanZero();
final Long receivablePenaltyAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue())
.value(receivablePenaltyAccountId).notNull().integerGreaterThanZero();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForSavingsProductCreate(final String json, DepositAccountType accountType) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_PRODUCT_RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
// accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed(accountingRuleParamName, element,
Locale.getDefault());
baseDataValidator.reset().parameter(accountingRuleParamName).value(accountingRuleType).notNull().inMinMaxRange(1, 3);
if (isCashBasedAccounting(accountingRuleType)) {
final Long savingsControlAccountId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId)
.notNull().integerGreaterThanZero();
final Long savingsReferenceAccountId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue()).value(savingsReferenceAccountId)
.notNull().integerGreaterThanZero();
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue())
.value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
final Long interestOnSavingsAccountId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue())
.value(interestOnSavingsAccountId).notNull().integerGreaterThanZero();
final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId).notNull()
.integerGreaterThanZero();
final Long incomeFromPenaltyId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
.notNull().integerGreaterThanZero();
final Boolean isDormancyTrackingActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
if (null != isDormancyTrackingActive && isDormancyTrackingActive) {
final Long escheatLiabilityId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue()).value(escheatLiabilityId)
.notNull().integerGreaterThanZero();
}
if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
final Long overdraftAccount = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue())
.value(overdraftAccount).notNull().integerGreaterThanZero();
final Long incomeFromInterest = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue()).value(incomeFromInterest)
.notNull().integerGreaterThanZero();
final Long writtenOff = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
element);
baseDataValidator.reset().parameter(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writtenOff).notNull()
.integerGreaterThanZero();
}
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForShareProductCreate(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(ShareProductApiConstants.SHARE_PRODUCT_RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
// accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed(accountingRuleParamName, element,
Locale.getDefault());
baseDataValidator.reset().parameter(accountingRuleParamName).value(accountingRuleType).notNull().inMinMaxRange(1, 3);
if (isCashBasedAccounting(accountingRuleType)) {
final Long shareReferenceId = this.fromApiJsonHelper.extractLongNamed(SharesProductAccountingParams.SHARES_REFERENCE.getValue(),
element);
baseDataValidator.reset().parameter(SharesProductAccountingParams.SHARES_REFERENCE.getValue()).value(shareReferenceId).notNull()
.integerGreaterThanZero();
final Long shareSuspenseId = this.fromApiJsonHelper.extractLongNamed(SharesProductAccountingParams.SHARES_SUSPENSE.getValue(),
element);
baseDataValidator.reset().parameter(SharesProductAccountingParams.SHARES_SUSPENSE.getValue()).value(shareSuspenseId).notNull()
.integerGreaterThanZero();
final Long incomeFromFeeAccountId = this.fromApiJsonHelper
.extractLongNamed(SharesProductAccountingParams.INCOME_FROM_FEES.getValue(), element);
baseDataValidator.reset().parameter(SharesProductAccountingParams.INCOME_FROM_FEES.getValue()).value(incomeFromFeeAccountId)
.notNull().integerGreaterThanZero();
final Long shareEquityId = this.fromApiJsonHelper.extractLongNamed(SharesProductAccountingParams.SHARES_EQUITY.getValue(),
element);
baseDataValidator.reset().parameter(SharesProductAccountingParams.SHARES_EQUITY.getValue()).value(shareEquityId).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_PERIODIC.getValue().equals(accountingRuleType)
|| AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
}
@@ -0,0 +1,390 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.service;
import com.google.gson.JsonElement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
import org.springframework.stereotype.Component;
@Component
public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper {
public LoanProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
final ChargeRepositoryWrapper chargeRepositoryWrapper, final GLAccountRepositoryWrapper accountRepositoryWrapper,
final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
paymentTypeRepositoryWrapper);
}
/***
* Set of abstractions for saving Loan Products to GL Account Mappings
***/
public void saveLoanToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET, PortfolioProductType.LOAN);
}
public void saveLoanToAssetOrLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
GLAccountType glAccountType = getGLAccountType(element, paramName, ASSET_LIABILITY_TYPES);
if (glAccountType != null) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, glAccountType, PortfolioProductType.LOAN);
}
}
public void saveLoanToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME, PortfolioProductType.LOAN);
}
public void saveLoanToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.LOAN);
}
public void saveLoanToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.LOAN);
}
/***
* Set of abstractions for merging Savings Products to GL Account Mappings
***/
public void mergeLoanToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET,
PortfolioProductType.LOAN);
}
public void mergeLoanToAssetOrLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
GLAccountType glAccountType = getGLAccountType(element, paramName, ASSET_LIABILITY_TYPES);
if (glAccountType != null) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, glAccountType,
PortfolioProductType.LOAN);
}
}
public void mergeLoanToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME,
PortfolioProductType.LOAN);
}
public void mergeLoanToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE,
PortfolioProductType.LOAN);
}
public void mergeLoanToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.LIABILITY,
PortfolioProductType.LOAN);
}
/*** Abstractions for payments channel related to loan products ***/
public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN);
}
public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN);
}
public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
// save both fee and penalty charges
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, true);
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false);
}
public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
// update both fee and penalty charges
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, true);
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false);
}
public Map<String, Object> populateChangesForNewLoanProductToGLAccountMappingCreation(final JsonElement element,
final AccountingRuleType accountingRuleType) {
final Map<String, Object> changes = new HashMap<>();
final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.FUND_SOURCE.getValue(), element);
final Long loanPortfolioAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(),
element);
final Long incomeFromInterestId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(),
element);
final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_FEES.getValue(),
element);
final Long incomeFromPenaltyId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), element);
final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
element);
final Long overPaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.OVERPAYMENT.getValue(),
element);
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
final Long receivableInterestAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), element);
final Long receivableFeeAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.FEES_RECEIVABLE.getValue(),
element);
final Long receivablePenaltyAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), element);
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, incomeFromFeeId,
incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
incomeFromRecoveryAccountId);
break;
case ACCRUAL_PERIODIC:
populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
incomeFromRecoveryAccountId, receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
break;
case ACCRUAL_UPFRONT:
populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
incomeFromRecoveryAccountId, receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
break;
}
return changes;
}
private void populateChangesForAccrualBasedAccounting(final Map<String, Object> changes, final Long fundAccountId,
final Long loanPortfolioAccountId, final Long incomeFromInterestId, final Long incomeFromFeeId, final Long incomeFromPenaltyId,
final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId,
final Long incomeFromRecoveryAccountId, final Long receivableInterestAccountId, final Long receivableFeeAccountId,
final Long receivablePenaltyAccountId) {
changes.put(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId);
changes.put(LoanProductAccountingParams.FEES_RECEIVABLE.getValue(), receivableFeeAccountId);
changes.put(LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId);
populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, incomeFromFeeId,
incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId, incomeFromRecoveryAccountId);
}
private void populateChangesForCashBasedAccounting(final Map<String, Object> changes, final Long fundAccountId,
final Long loanPortfolioAccountId, final Long incomeFromInterestId, final Long incomeFromFeeId, final Long incomeFromPenaltyId,
final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId,
final Long incomeFromRecoveryAccountId) {
changes.put(LoanProductAccountingParams.FUND_SOURCE.getValue(), fundAccountId);
changes.put(LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId);
changes.put(LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), incomeFromInterestId);
changes.put(LoanProductAccountingParams.INCOME_FROM_FEES.getValue(), incomeFromFeeId);
changes.put(LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId);
changes.put(LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId);
changes.put(LoanProductAccountingParams.OVERPAYMENT.getValue(), overPaymentAccountId);
changes.put(LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
changes.put(LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), incomeFromRecoveryAccountId);
}
/**
* 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
*/
public void handleChangesToLoanProductToGLAccountMappings(final Long loanProductId, final Map<String, Object> changes,
final JsonElement element, final AccountingRuleType accountingRuleType) {
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
// asset or liabilities
mergeLoanToAssetOrLiabilityAccountMappingChanges(element, LoanProductAccountingParams.FUND_SOURCE.getValue(), loanProductId,
CashAccountsForLoan.FUND_SOURCE.getValue(), CashAccountsForLoan.FUND_SOURCE.toString(), changes);
// asset
mergeLoanToAssetAccountMappingChanges(element, LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(), loanProductId,
CashAccountsForLoan.LOAN_PORTFOLIO.getValue(), CashAccountsForLoan.LOAN_PORTFOLIO.toString(), changes);
mergeLoanToAssetAccountMappingChanges(element, LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), loanProductId,
CashAccountsForLoan.TRANSFERS_SUSPENSE.getValue(), CashAccountsForLoan.TRANSFERS_SUSPENSE.toString(), changes);
// income
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), loanProductId,
CashAccountsForLoan.INTEREST_ON_LOANS.getValue(), CashAccountsForLoan.INTEREST_ON_LOANS.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_FEES.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_FEES.getValue(), CashAccountsForLoan.INCOME_FROM_FEES.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), CashAccountsForLoan.INCOME_FROM_PENALTIES.toString(),
changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), CashAccountsForLoan.INCOME_FROM_RECOVERY.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
loanProductId, CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
loanProductId, CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
loanProductId, CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(),
loanProductId, CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(),
CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(),
loanProductId, CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(),
CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
loanProductId, CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY.toString(), changes);
// expenses
mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
CashAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), CashAccountsForLoan.LOSSES_WRITTEN_OFF.toString(), changes);
mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId,
CashAccountsForLoan.GOODWILL_CREDIT.getValue(), CashAccountsForLoan.GOODWILL_CREDIT.toString(), changes);
mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), loanProductId,
CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), CashAccountsForLoan.CHARGE_OFF_EXPENSE.toString(), changes);
mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
loanProductId, CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.toString(), changes);
// liabilities
mergeLoanToLiabilityAccountMappingChanges(element, LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId,
CashAccountsForLoan.OVERPAYMENT.getValue(), CashAccountsForLoan.OVERPAYMENT.toString(), changes);
break;
case ACCRUAL_UPFRONT:
// fall through to periodic accrual
case ACCRUAL_PERIODIC:
// asset or liabilities
mergeLoanToAssetOrLiabilityAccountMappingChanges(element, LoanProductAccountingParams.FUND_SOURCE.getValue(), loanProductId,
CashAccountsForLoan.FUND_SOURCE.getValue(), CashAccountsForLoan.FUND_SOURCE.toString(), changes);
// assets (including receivables)
mergeLoanToAssetAccountMappingChanges(element, LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(), loanProductId,
AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), AccrualAccountsForLoan.LOAN_PORTFOLIO.toString(), changes);
mergeLoanToAssetAccountMappingChanges(element, LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), loanProductId,
AccrualAccountsForLoan.TRANSFERS_SUSPENSE.getValue(), AccrualAccountsForLoan.TRANSFERS_SUSPENSE.toString(),
changes);
mergeLoanToAssetAccountMappingChanges(element, LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), loanProductId,
AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), AccrualAccountsForLoan.INTEREST_RECEIVABLE.toString(),
changes);
mergeLoanToAssetAccountMappingChanges(element, LoanProductAccountingParams.FEES_RECEIVABLE.getValue(), loanProductId,
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), AccrualAccountsForLoan.FEES_RECEIVABLE.toString(), changes);
mergeLoanToAssetAccountMappingChanges(element, LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), loanProductId,
AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), AccrualAccountsForLoan.PENALTIES_RECEIVABLE.toString(),
changes);
// income
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), loanProductId,
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_FEES.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), AccrualAccountsForLoan.INCOME_FROM_FEES.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), AccrualAccountsForLoan.INCOME_FROM_PENALTIES.toString(),
changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), AccrualAccountsForLoan.INCOME_FROM_RECOVERY.toString(),
changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
loanProductId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
loanProductId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
loanProductId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(),
loanProductId, AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(),
AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(),
loanProductId, AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(),
AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
loanProductId, AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY.toString(), changes);
// expenses
mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.toString(),
changes);
mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId,
AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(), AccrualAccountsForLoan.GOODWILL_CREDIT.toString(), changes);
mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), loanProductId,
AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.toString(),
changes);
mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
loanProductId, AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.toString(), changes);
// liabilities
mergeLoanToLiabilityAccountMappingChanges(element, LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId,
CashAccountsForLoan.OVERPAYMENT.getValue(), CashAccountsForLoan.OVERPAYMENT.toString(), changes);
break;
}
}
public void deleteLoanProductToGLAccountMapping(final Long loanProductId) {
deleteProductToGLAccountMapping(loanProductId, PortfolioProductType.LOAN);
}
private GLAccountType getGLAccountType(final JsonElement element, final String paramName, final List<GLAccountType> allowedTypes) {
GLAccountType gLAccountType = null;
final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element);
if (accountId != null) {
final GLAccount glAccount = getAccountById(paramName, accountId);
gLAccountType = GLAccountType.fromInt(glAccount.getType());
if (!allowedTypes.contains(gLAccountType)) {
throw new ProductToGLAccountMappingInvalidException(paramName, glAccount.getName(), accountId, gLAccountType.toString(),
GLAccountType.ASSET.getCode() + " or " + GLAccountType.LIABILITY.getCode());
}
}
return gLAccountType;
}
}
@@ -0,0 +1,463 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException;
import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ProductToGLAccountMappingHelper {
protected static final List<GLAccountType> ASSET_LIABILITY_TYPES = List.of(GLAccountType.ASSET, GLAccountType.LIABILITY);
protected final GLAccountRepository accountRepository;
protected final ProductToGLAccountMappingRepository accountMappingRepository;
protected final FromJsonHelper fromApiJsonHelper;
private final ChargeRepositoryWrapper chargeRepositoryWrapper;
protected final GLAccountRepositoryWrapper accountRepositoryWrapper;
private final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper;
public void saveProductToAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId, final GLAccountType expectedAccountType, final PortfolioProductType portfolioProductType) {
final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element);
if (accountId != null) { // optional entries may be null
final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId);
final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId)
.setProductType(portfolioProductType.getValue()).setFinancialAccountType(placeHolderTypeId);
this.accountMappingRepository.saveAndFlush(accountMapping);
}
}
public void mergeProductToAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes,
final GLAccountType expectedAccountType, final PortfolioProductType portfolioProductType) {
final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element);
// get the existing product
if (accountId != null) {
final ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(productId,
portfolioProductType.getValue(), accountTypeId);
if (accountMapping == null) {
ArrayList<String> optionalProductToGLAccountMappingEntries = new ArrayList<String>();
optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.GOODWILL_CREDIT.getValue());
optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue());
optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue());
optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue());
optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue());
optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue());
optionalProductToGLAccountMappingEntries.add("incomeFromGoodwillCreditInterestAccountId");
optionalProductToGLAccountMappingEntries.add("incomeFromGoodwillCreditFeesAccountId");
optionalProductToGLAccountMappingEntries.add("incomeFromGoodwillCreditPenaltyAccountId");
if (optionalProductToGLAccountMappingEntries.contains(paramName)) {
saveProductToAccountMapping(element, paramName, productId, accountTypeId, expectedAccountType, portfolioProductType);
} else {
throw new ProductToGLAccountMappingNotFoundException(portfolioProductType, productId, accountTypeName);
}
} else {
if (!accountMapping.getGlAccount().getId().equals(accountId)) {
final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId);
changes.put(paramName, accountId);
accountMapping.setGlAccount(glAccount);
this.accountMappingRepository.saveAndFlush(accountMapping);
}
}
}
}
public void createOrmergeProductToAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final Map<String, Object> changes, final GLAccountType expectedAccountType,
final PortfolioProductType portfolioProductType) {
final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element);
// get the existing product
if (accountId != null) {
final ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(productId,
portfolioProductType.getValue(), accountTypeId);
if (accountMapping == null) {
final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId);
changes.put(paramName, accountId);
ProductToGLAccountMapping newAccountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount)
.setProductId(productId).setProductType(portfolioProductType.getValue()).setFinancialAccountType(accountTypeId);
this.accountMappingRepository.saveAndFlush(newAccountMapping);
} else if (!accountMapping.getGlAccount().getId().equals(accountId)) {
final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId);
changes.put(paramName, accountId);
accountMapping.setGlAccount(glAccount);
this.accountMappingRepository.saveAndFlush(accountMapping);
}
}
}
/**
* Saves the payment type to Fund source mappings for a particular product/product type (also populates the changes
* array if passed in)
*
* @param command
* @param element
* @param productId
* @param changes
*/
public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes, final PortfolioProductType portfolioProductType) {
final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper
.extractJsonArrayNamed(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
if (paymentChannelMappingArray != null) {
if (changes != null) {
changes.put(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
command.jsonFragment(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue()));
}
for (int i = 0; i < paymentChannelMappingArray.size(); i++) {
final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject();
final Long paymentTypeId = jsonObject.get(LoanProductAccountingParams.PAYMENT_TYPE.getValue()).getAsLong();
final Long paymentSpecificFundAccountId = jsonObject.get(LoanProductAccountingParams.FUND_SOURCE.getValue()).getAsLong();
savePaymentChannelToFundSourceMapping(productId, paymentTypeId, paymentSpecificFundAccountId, portfolioProductType);
}
}
}
/**
* Saves the Charge to Income / Liability account mappings for a particular product/product type (also populates the
* changes array if passed in)
*
* @param command
* @param element
* @param productId
* @param changes
*/
public void saveChargesToGLAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes, final PortfolioProductType portfolioProductType, final boolean isPenalty) {
String arrayName;
if (isPenalty) {
arrayName = LoanProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
} else {
arrayName = LoanProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue();
}
final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayName, element);
if (chargeToIncomeAccountMappingArray != null) {
if (changes != null) {
changes.put(LoanProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
command.jsonFragment(LoanProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue()));
}
for (int i = 0; i < chargeToIncomeAccountMappingArray.size(); i++) {
final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
final Long chargeId = jsonObject.get(LoanProductAccountingParams.CHARGE_ID.getValue()).getAsLong();
final Long incomeAccountId = jsonObject.get(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue()).getAsLong();
saveChargeToFundSourceMapping(productId, chargeId, incomeAccountId, portfolioProductType, isPenalty);
}
}
}
/**
* @param command
* @param element
* @param productId
* @param changes
*/
public void updateChargeToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes, final PortfolioProductType portfolioProductType, final boolean isPenalty) {
// find all existing payment Channel to Fund source Mappings
List<ProductToGLAccountMapping> existingChargeToIncomeAccountMappings;
String arrayFragmentName;
if (isPenalty) {
existingChargeToIncomeAccountMappings = this.accountMappingRepository.findAllPenaltyToIncomeAccountMappings(productId,
portfolioProductType.getValue());
arrayFragmentName = LoanProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
} else {
existingChargeToIncomeAccountMappings = this.accountMappingRepository.findAllFeeToIncomeAccountMappings(productId,
portfolioProductType.getValue());
arrayFragmentName = LoanProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue();
}
final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element);
/**
* Variable stores a map representation of charges (key) and their associated income Id's (value) extracted from
* the passed in Jsoncommand
**/
final Map<Long, Long> inputChargeToIncomeAccountMap = new HashMap<>();
/***
* Variable stores all charges which have already been mapped to Income Accounts in the system
**/
final Set<Long> existingCharges = new HashSet<>();
if (chargeToIncomeAccountMappingArray != null) {
if (changes != null) {
changes.put(arrayFragmentName, command.jsonFragment(arrayFragmentName));
}
for (int i = 0; i < chargeToIncomeAccountMappingArray.size(); i++) {
final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
final Long chargeId = jsonObject.get(LoanProductAccountingParams.CHARGE_ID.getValue()).getAsLong();
final Long incomeAccountId = jsonObject.get(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue()).getAsLong();
inputChargeToIncomeAccountMap.put(chargeId, incomeAccountId);
}
// If input map is empty, delete all existing mappings
if (inputChargeToIncomeAccountMap.size() == 0) {
this.accountMappingRepository.deleteAllInBatch(existingChargeToIncomeAccountMappings);
} /**
* Else, <br/>
* update existing mappings OR <br/>
* delete old mappings (which are already present, but not passed in as a part of Jsoncommand)<br/>
* Create new mappings for charges that are passed in as a part of the Jsoncommand but not already present
*
**/
else {
for (final ProductToGLAccountMapping chargeToIncomeAccountMapping : existingChargeToIncomeAccountMappings) {
final Long currentCharge = chargeToIncomeAccountMapping.getCharge().getId();
existingCharges.add(currentCharge);
// update existing mappings (if required)
if (inputChargeToIncomeAccountMap.containsKey(currentCharge)) {
final Long newGLAccountId = inputChargeToIncomeAccountMap.get(currentCharge);
if (!newGLAccountId.equals(chargeToIncomeAccountMapping.getGlAccount().getId())) {
final GLAccount glAccount;
if (isPenalty) {
glAccount = getAccountByIdAndType(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(),
GLAccountType.INCOME, newGLAccountId);
} else {
List<GLAccountType> allowedAccountTypes = getAllowedAccountTypesForFeeMapping();
glAccount = getAccountByIdAndType(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(),
allowedAccountTypes, newGLAccountId);
}
chargeToIncomeAccountMapping.setGlAccount(glAccount);
this.accountMappingRepository.saveAndFlush(chargeToIncomeAccountMapping);
}
} // deleted payment type
else {
this.accountMappingRepository.delete(chargeToIncomeAccountMapping);
}
}
// create new mappings
final Set<Long> incomingCharges = inputChargeToIncomeAccountMap.keySet();
incomingCharges.removeAll(existingCharges);
// incomingPaymentTypes now only contains the newly added
// payment Type mappings
for (final Long newCharge : incomingCharges) {
final Long newGLAccountId = inputChargeToIncomeAccountMap.get(newCharge);
saveChargeToFundSourceMapping(productId, newCharge, newGLAccountId, portfolioProductType, isPenalty);
}
}
}
}
/**
* @param command
* @param element
* @param productId
* @param changes
*/
public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes, final PortfolioProductType portfolioProductType) {
// find all existing payment Channel to Fund source Mappings
final List<ProductToGLAccountMapping> existingPaymentChannelToFundSourceMappings = this.accountMappingRepository
.findAllPaymentTypeToFundSourceMappings(productId, portfolioProductType.getValue());
final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper
.extractJsonArrayNamed(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
/**
* Variable stores a map representation of Payment channels (key) and their fund sources (value) extracted from
* the passed in Jsoncommand
**/
final Map<Long, Long> inputPaymentChannelFundSourceMap = new HashMap<>();
/***
* Variable stores all payment types which have already been mapped to Fund Sources in the system
**/
final Set<Long> existingPaymentTypes = new HashSet<>();
if (paymentChannelMappingArray != null) {
if (changes != null) {
changes.put(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
command.jsonFragment(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue()));
}
for (int i = 0; i < paymentChannelMappingArray.size(); i++) {
final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject();
final Long paymentTypeId = jsonObject.get(LoanProductAccountingParams.PAYMENT_TYPE.getValue()).getAsLong();
final Long paymentSpecificFundAccountId = jsonObject.get(LoanProductAccountingParams.FUND_SOURCE.getValue()).getAsLong();
inputPaymentChannelFundSourceMap.put(paymentTypeId, paymentSpecificFundAccountId);
}
// If input map is empty, delete all existing mappings
if (inputPaymentChannelFundSourceMap.size() == 0) {
this.accountMappingRepository.deleteAllInBatch(existingPaymentChannelToFundSourceMappings);
} /**
* Else, <br/>
* update existing mappings OR <br/>
* delete old mappings (which re already present, but not passed in as a part of Jsoncommand)<br/>
* Create new mappings for payment types that are passed in as a part of the Jsoncommand but not already
* present
*
**/
else {
for (final ProductToGLAccountMapping existingPaymentChannelToFundSourceMapping : existingPaymentChannelToFundSourceMappings) {
final Long currentPaymentChannelId = existingPaymentChannelToFundSourceMapping.getPaymentType().getId();
existingPaymentTypes.add(currentPaymentChannelId);
// update existing mappings (if required)
if (inputPaymentChannelFundSourceMap.containsKey(currentPaymentChannelId)) {
final Long newGLAccountId = inputPaymentChannelFundSourceMap.get(currentPaymentChannelId);
if (!newGLAccountId.equals(existingPaymentChannelToFundSourceMapping.getGlAccount().getId())) {
final GLAccount glAccount = getAccountById(LoanProductAccountingParams.FUND_SOURCE.getValue(), newGLAccountId);
existingPaymentChannelToFundSourceMapping.setGlAccount(glAccount);
this.accountMappingRepository.saveAndFlush(existingPaymentChannelToFundSourceMapping);
}
} // deleted payment type
else {
this.accountMappingRepository.delete(existingPaymentChannelToFundSourceMapping);
}
}
// create new mappings
final Set<Long> incomingPaymentTypes = inputPaymentChannelFundSourceMap.keySet();
incomingPaymentTypes.removeAll(existingPaymentTypes);
// incomingPaymentTypes now only contains the newly added
// payment Type mappings
for (final Long newPaymentType : incomingPaymentTypes) {
final Long newGLAccountId = inputPaymentChannelFundSourceMap.get(newPaymentType);
savePaymentChannelToFundSourceMapping(productId, newPaymentType, newGLAccountId, portfolioProductType);
}
}
}
}
/**
* @param productId
*
*/
private void savePaymentChannelToFundSourceMapping(final Long productId, final Long paymentTypeId,
final Long paymentTypeSpecificFundAccountId, final PortfolioProductType portfolioProductType) {
final PaymentType paymentType = this.paymentTypeRepositoryWrapper.findOneWithNotFoundDetection(paymentTypeId);
final GLAccount glAccount = getAccountById(LoanProductAccountingParams.FUND_SOURCE.getValue(), paymentTypeSpecificFundAccountId);
final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId)
.setProductType(portfolioProductType.getValue()).setFinancialAccountType(CashAccountsForLoan.FUND_SOURCE.getValue())
.setPaymentType(paymentType);
this.accountMappingRepository.saveAndFlush(accountMapping);
}
/**
* @param productId
*
*/
private void saveChargeToFundSourceMapping(final Long productId, final Long chargeId, final Long incomeAccountId,
final PortfolioProductType portfolioProductType, final boolean isPenalty) {
final Charge charge = this.chargeRepositoryWrapper.findOneWithNotFoundDetection(chargeId);
// TODO Vishwas: Need to validate if given charge is fee or Penalty
// based on input condition
GLAccount glAccount;
/**
* Both CASH and Accrual placeholders have the same value for income from Interest and penalties
**/
CashAccountsForLoan placeHolderAccountType;
if (isPenalty) {
glAccount = getAccountByIdAndType(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), GLAccountType.INCOME,
incomeAccountId);
placeHolderAccountType = CashAccountsForLoan.INCOME_FROM_PENALTIES;
} else {
List<GLAccountType> allowedAccountTypes = getAllowedAccountTypesForFeeMapping();
glAccount = getAccountByIdAndType(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), allowedAccountTypes,
incomeAccountId);
placeHolderAccountType = CashAccountsForLoan.INCOME_FROM_FEES;
}
final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId)
.setProductType(portfolioProductType.getValue()).setFinancialAccountType(placeHolderAccountType.getValue())
.setCharge(charge);
this.accountMappingRepository.saveAndFlush(accountMapping);
}
private List<GLAccountType> getAllowedAccountTypesForFeeMapping() {
List<GLAccountType> allowedAccountTypes = new ArrayList<>();
allowedAccountTypes.add(GLAccountType.INCOME);
allowedAccountTypes.add(GLAccountType.LIABILITY);
return allowedAccountTypes;
}
/**
* Fetches account with a particular Id and throws and Exception it is not of the expected Account Category
* ('ASSET','liability' etc)
*
* @param paramName
* @param expectedAccountType
* @param accountId
* @return
*/
public GLAccount getAccountByIdAndType(final String paramName, final GLAccountType expectedAccountType, final Long accountId) {
final GLAccount glAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId);
// validate account is of the expected Type
if (glAccount.getType().intValue() != expectedAccountType.getValue()) {
throw new ProductToGLAccountMappingInvalidException(paramName, glAccount.getName(), accountId,
GLAccountType.fromInt(glAccount.getType()).toString(), expectedAccountType.toString());
}
return glAccount;
}
public GLAccount getAccountById(final String paramName, final Long accountId) {
final GLAccount glAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId);
return glAccount;
}
public GLAccount getAccountByIdAndType(final String paramName, final List<GLAccountType> expectedAccountTypes, final Long accountId) {
final GLAccount glAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId);
// validate account is of the expected Type
List<Integer> glAccountTypeValues = new ArrayList<>();
for (GLAccountType glAccountType : expectedAccountTypes) {
glAccountTypeValues.add(glAccountType.getValue());
}
if (!glAccountTypeValues.contains(glAccount.getType())) {
throw new ProductToGLAccountMappingInvalidException(paramName, glAccount.getName(), accountId,
GLAccountType.fromInt(glAccount.getType()).toString(), glAccountTypeValues.toString());
}
return glAccount;
}
public void deleteProductToGLAccountMapping(final Long loanProductId, final PortfolioProductType portfolioProductType) {
final List<ProductToGLAccountMapping> productToGLAccountMappings = this.accountMappingRepository
.findByProductIdAndProductType(loanProductId, portfolioProductType.getValue());
if (productToGLAccountMappings != null && productToGLAccountMappings.size() > 0) {
this.accountMappingRepository.deleteAllInBatch(productToGLAccountMappings);
}
}
}
@@ -0,0 +1,50 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.service;
import java.util.List;
import java.util.Map;
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
public interface ProductToGLAccountMappingReadPlatformService {
Map<String, Object> fetchAccountMappingDetailsForLoanProduct(Long loanProductId, Integer accountingType);
List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForLoanProduct(Long loanProductId);
List<ChargeToGLAccountMapper> fetchFeeToGLAccountMappingsForLoanProduct(Long loanProductId);
List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForLoanProduct(Long loanProductId);
Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(Long savingsProductId, Integer accountingType);
List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(Long savingsProductId);
List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForSavingsProduct(Long savingsProductId);
List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForSavingsProduct(Long savingsProductId);
Map<String, Object> fetchAccountMappingDetailsForShareProduct(Long productId, Integer accountingType);
List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForShareProduct(Long productId);
List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForShareProduct(Long productId);
}
@@ -0,0 +1,415 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares;
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingDataParams;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingDataParams;
import org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ProductToGLAccountMappingReadPlatformServiceImpl implements ProductToGLAccountMappingReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private static final class ProductToGLAccountMappingMapper implements RowMapper<Map<String, Object>> {
public String schema() {
return " mapping.id as id, mapping.gl_account_id as glAccountId,glaccount.name as name,glaccount.gl_code as code,"
+ " mapping.product_id as productId, mapping.product_type as productType,mapping.financial_account_type as financialAccountType, "
+ " mapping.payment_type as paymentTypeId,pt.value as paymentTypeValue, mapping.charge_id as chargeId, charge.is_penalty as penalty, "
+ " charge.name as chargeName "
+ " from acc_product_mapping mapping left join m_charge charge on mapping.charge_id=charge.id "
+ " left join acc_gl_account as glaccount on mapping.gl_account_id = glaccount.id"
+ " left join m_payment_type pt on mapping.payment_type=pt.id" + " where mapping.product_type= ? ";
}
@Override
public Map<String, Object> mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long glAccountId = rs.getLong("glAccountId");
final Long productId = rs.getLong("productId");
final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentTypeId");
final Long chargeId = rs.getLong("chargeId");
final Integer productType = rs.getInt("productType");
final String paymentTypeValue = rs.getString("paymentTypeValue");
final Integer financialAccountType = rs.getInt("financialAccountType");
final String glAccountName = rs.getString("name");
final String glCode = rs.getString("code");
final String chargeName = rs.getString("chargeName");
final Boolean penalty = rs.getBoolean("penalty");
final Map<String, Object> loanProductToGLAccountMap = new LinkedHashMap<>(5);
loanProductToGLAccountMap.put("id", id);
loanProductToGLAccountMap.put("glAccountId", glAccountId);
loanProductToGLAccountMap.put("productId", productId);
loanProductToGLAccountMap.put("productType", productType);
loanProductToGLAccountMap.put("financialAccountType", financialAccountType);
loanProductToGLAccountMap.put("paymentTypeId", paymentTypeId);
loanProductToGLAccountMap.put("paymentTypeValue", paymentTypeValue);
loanProductToGLAccountMap.put("chargeId", chargeId);
loanProductToGLAccountMap.put("chargeName", chargeName);
loanProductToGLAccountMap.put("penalty", penalty);
loanProductToGLAccountMap.put("glAccountName", glAccountName);
loanProductToGLAccountMap.put("glCode", glCode);
return loanProductToGLAccountMap;
}
}
@Override
public Map<String, Object> fetchAccountMappingDetailsForLoanProduct(final Long loanProductId, final Integer accountingType) {
final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8);
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null";
final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR
new Object[] { PortfolioProductType.LOAN.getValue(), loanProductId });
if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) {
for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
final CashAccountsForLoan glAccountForLoan = CashAccountsForLoan.fromInt(financialAccountType);
final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
final String glCode = (String) productToGLAccountMap.get("glCode");
final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
if (glAccountForLoan.equals(CashAccountsForLoan.FUND_SOURCE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.FUND_SOURCE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_FEES)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_FEES.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_PENALTIES)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INTEREST_ON_LOANS)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_ON_LOANS.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.LOAN_PORTFOLIO)) {
accountMappingDetails.put(LoanProductAccountingDataParams.LOAN_PORTFOLIO.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.TRANSFERS_SUSPENSE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.LOSSES_WRITTEN_OFF)) {
accountMappingDetails.put(LoanProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.GOODWILL_CREDIT)) {
accountMappingDetails.put(LoanProductAccountingDataParams.GOODWILL_CREDIT.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.OVERPAYMENT)) {
accountMappingDetails.put(LoanProductAccountingDataParams.OVERPAYMENT.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_RECOVERY)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_RECOVERY.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.CHARGE_OFF_EXPENSE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_EXPENSE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(),
gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
gLAccountData);
}
}
} else if (AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingType)
|| AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingType)) {
for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
final AccrualAccountsForLoan glAccountForLoan = AccrualAccountsForLoan.fromInt(financialAccountType);
final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
final String glCode = (String) productToGLAccountMap.get("glCode");
final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
if (glAccountForLoan.equals(AccrualAccountsForLoan.FUND_SOURCE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.FUND_SOURCE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_FEES)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_FEES.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_PENALTIES)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INTEREST_ON_LOANS)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_ON_LOANS.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.LOAN_PORTFOLIO)) {
accountMappingDetails.put(LoanProductAccountingDataParams.LOAN_PORTFOLIO.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.OVERPAYMENT)) {
accountMappingDetails.put(LoanProductAccountingDataParams.OVERPAYMENT.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.TRANSFERS_SUSPENSE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.LOSSES_WRITTEN_OFF)) {
accountMappingDetails.put(LoanProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.GOODWILL_CREDIT)) {
accountMappingDetails.put(LoanProductAccountingDataParams.GOODWILL_CREDIT.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INTEREST_RECEIVABLE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_RECEIVABLE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.FEES_RECEIVABLE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.FEES_RECEIVABLE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.PENALTIES_RECEIVABLE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.PENALTIES_RECEIVABLE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_RECOVERY)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_RECOVERY.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.CHARGE_OFF_EXPENSE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_EXPENSE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE)) {
accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(),
gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY)) {
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
gLAccountData);
}
}
}
return accountMappingDetails;
}
@Override
public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType) {
final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8);
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null ";
final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR
new Object[] { PortfolioProductType.SAVING.getValue(), savingsProductId });
if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) {
for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
final CashAccountsForSavings glAccountForSavings = CashAccountsForSavings.fromInt(financialAccountType);
final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
final String glCode = (String) productToGLAccountMap.get("glCode");
final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_REFERENCE)) {
accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_REFERENCE.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_CONTROL)) {
accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_CONTROL.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_FEES)) {
accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_FEES.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_PENALTIES)) {
accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.TRANSFERS_SUSPENSE)) {
accountMappingDetails.put(SavingProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.INTEREST_ON_SAVINGS)) {
accountMappingDetails.put(SavingProductAccountingDataParams.INTEREST_ON_SAVINGS.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL)) {
accountMappingDetails.put(SavingProductAccountingDataParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.LOSSES_WRITTEN_OFF)) {
accountMappingDetails.put(SavingProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_INTEREST)) {
accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_INTEREST.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CashAccountsForSavings.ESCHEAT_LIABILITY)) {
accountMappingDetails.put(SavingProductAccountingDataParams.ESCHEAT_LIABILITY.getValue(), gLAccountData);
}
}
}
return accountMappingDetails;
}
@Override
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForLoanProduct(final Long loanProductId) {
return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.LOAN, loanProductId);
}
@Override
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId) {
return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.SAVING, savingsProductId);
}
/**
* @param loanProductId
* @return
*/
private List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappings(final PortfolioProductType portfolioProductType,
final Long loanProductId) {
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is not null";
final List<Map<String, Object>> paymentTypeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, // NOSONAR
new Object[] { portfolioProductType.getValue(), loanProductId });
List<PaymentTypeToGLAccountMapper> paymentTypeToGLAccountMappers = null;
for (final Map<String, Object> productToGLAccountMap : paymentTypeToFundSourceMappingsList) {
if (paymentTypeToGLAccountMappers == null) {
paymentTypeToGLAccountMappers = new ArrayList<>();
}
final Long paymentTypeId = (Long) productToGLAccountMap.get("paymentTypeId");
final String paymentTypeValue = (String) productToGLAccountMap.get("paymentTypeValue");
final PaymentTypeData paymentTypeData = PaymentTypeData.instance(paymentTypeId, paymentTypeValue);
final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
final String glCode = (String) productToGLAccountMap.get("glCode");
final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
final PaymentTypeToGLAccountMapper paymentTypeToGLAccountMapper = new PaymentTypeToGLAccountMapper()
.setPaymentType(paymentTypeData).setFundSourceAccount(gLAccountData);
paymentTypeToGLAccountMappers.add(paymentTypeToGLAccountMapper);
}
return paymentTypeToGLAccountMappers;
}
@Override
public List<ChargeToGLAccountMapper> fetchFeeToGLAccountMappingsForLoanProduct(final Long loanProductId) {
return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, false);
}
@Override
public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForLoanProduct(final Long loanProductId) {
return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, true);
}
@Override
public List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) {
return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, false);
}
@Override
public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) {
return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, true);
}
private List<ChargeToGLAccountMapper> fetchChargeToIncomeAccountMappings(final PortfolioProductType portfolioProductType,
final Long loanProductId, final boolean penalty) {
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
String sql = "select " + rm.schema() + " and product_id = ? and mapping.charge_id is not null and charge.is_penalty=";
if (penalty) {
sql = sql + " true";
} else {
sql = sql + " false";
}
final List<Map<String, Object>> chargeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, // NOSONAR
new Object[] { portfolioProductType.getValue(), loanProductId });
List<ChargeToGLAccountMapper> chargeToGLAccountMappers = null;
for (final Map<String, Object> chargeToIncomeAccountMap : chargeToFundSourceMappingsList) {
if (chargeToGLAccountMappers == null) {
chargeToGLAccountMappers = new ArrayList<>();
}
final Long glAccountId = (Long) chargeToIncomeAccountMap.get("glAccountId");
final String glAccountName = (String) chargeToIncomeAccountMap.get("glAccountName");
final String glCode = (String) chargeToIncomeAccountMap.get("glCode");
final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
final Long chargeId = (Long) chargeToIncomeAccountMap.get("chargeId");
final String chargeName = (String) chargeToIncomeAccountMap.get("chargeName");
final Boolean penalty1 = (Boolean) chargeToIncomeAccountMap.get("penalty");
final ChargeData chargeData = ChargeData.lookup(chargeId, chargeName, penalty1);
final ChargeToGLAccountMapper chargeToGLAccountMapper = new ChargeToGLAccountMapper().setCharge(chargeData)
.setIncomeAccount(gLAccountData);
chargeToGLAccountMappers.add(chargeToGLAccountMapper);
}
return chargeToGLAccountMappers;
}
@Override
public Map<String, Object> fetchAccountMappingDetailsForShareProduct(Long productId, Integer accountingType) {
final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8);
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null ";
final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR
new Object[] { PortfolioProductType.SHARES.getValue(), productId });
if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) {
for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
final CashAccountsForShares glAccountForShares = CashAccountsForShares.fromInt(financialAccountType);
final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
final String glCode = (String) productToGLAccountMap.get("glCode");
final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
if (glAccountForShares.equals(CashAccountsForShares.SHARES_REFERENCE)) {
accountMappingDetails.put(SharesProductAccountingParams.SHARES_REFERENCE.getValue(), gLAccountData);
} else if (glAccountForShares.equals(CashAccountsForShares.SHARES_SUSPENSE)) {
accountMappingDetails.put(SharesProductAccountingParams.SHARES_SUSPENSE.getValue(), gLAccountData);
} else if (glAccountForShares.equals(CashAccountsForShares.INCOME_FROM_FEES)) {
accountMappingDetails.put(SharesProductAccountingParams.INCOME_FROM_FEES.getValue(), gLAccountData);
} else if (glAccountForShares.equals(CashAccountsForShares.SHARES_EQUITY)) {
accountMappingDetails.put(SharesProductAccountingParams.SHARES_EQUITY.getValue(), gLAccountData);
}
}
}
return accountMappingDetails;
}
@Override
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForShareProduct(Long productId) {
return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.SHARES, productId);
}
@Override
public List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForShareProduct(Long productId) {
return fetchChargeToIncomeAccountMappings(PortfolioProductType.SHARES, productId, false);
}
}
@@ -0,0 +1,42 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.service;
import java.util.Map;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.portfolio.savings.DepositAccountType;
public interface ProductToGLAccountMappingWritePlatformService {
void createLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command);
void createSavingProductToGLAccountMapping(Long savingProductId, JsonCommand command, DepositAccountType accountType);
Map<String, Object> updateLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command, boolean accountingRuleChanged,
int accountingRuleTypeId);
Map<String, Object> updateSavingsProductToGLAccountMapping(Long savingsProductId, JsonCommand command, boolean accountingRuleChanged,
int accountingRuleTypeId, DepositAccountType accountType);
void createShareProductToGLAccountMapping(Long shareProductId, JsonCommand command);
Map<String, Object> updateShareProductToGLAccountMapping(Long shareProductId, JsonCommand command, boolean accountingRuleChanged,
int accountingRuleTypeId);
}
@@ -0,0 +1,423 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.service;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import com.google.gson.JsonElement;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares;
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.producttoaccountmapping.serialization.ProductToGLAccountMappingFromApiJsonDeserializer;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class ProductToGLAccountMappingWritePlatformServiceImpl implements ProductToGLAccountMappingWritePlatformService {
private final FromJsonHelper fromApiJsonHelper;
private final ProductToGLAccountMappingFromApiJsonDeserializer deserializer;
private final LoanProductToGLAccountMappingHelper loanProductToGLAccountMappingHelper;
private final SavingsProductToGLAccountMappingHelper savingsProductToGLAccountMappingHelper;
private final ShareProductToGLAccountMappingHelper shareProductToGLAccountMappingHelper;
@Override
@Transactional
public void createLoanProductToGLAccountMapping(final Long loanProductId, final JsonCommand command) {
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final Integer accountingRuleTypeId = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
// asset or liability
this.loanProductToGLAccountMappingHelper.saveLoanToAssetOrLiabilityAccountMapping(element,
LoanProductAccountingParams.FUND_SOURCE.getValue(), loanProductId, CashAccountsForLoan.FUND_SOURCE.getValue());
// asset
this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(), loanProductId,
CashAccountsForLoan.LOAN_PORTFOLIO.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), loanProductId,
CashAccountsForLoan.TRANSFERS_SUSPENSE.getValue());
// income
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), loanProductId,
CashAccountsForLoan.INTEREST_ON_LOANS.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_FEES.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_FEES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(), loanProductId,
CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue());
// expenses
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
CashAccountsForLoan.LOSSES_WRITTEN_OFF.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId,
CashAccountsForLoan.GOODWILL_CREDIT.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), loanProductId,
CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), loanProductId,
CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue());
// liabilities
this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element,
LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId, CashAccountsForLoan.OVERPAYMENT.getValue());
// advanced accounting mappings
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
break;
case ACCRUAL_UPFRONT:
// Fall Through
case ACCRUAL_PERIODIC:
// asset or liability
this.loanProductToGLAccountMappingHelper.saveLoanToAssetOrLiabilityAccountMapping(element,
LoanProductAccountingParams.FUND_SOURCE.getValue(), loanProductId, CashAccountsForLoan.FUND_SOURCE.getValue());
// assets (including receivables)
this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(), loanProductId,
AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), loanProductId,
AccrualAccountsForLoan.TRANSFERS_SUSPENSE.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), loanProductId,
AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LoanProductAccountingParams.FEES_RECEIVABLE.getValue(), loanProductId,
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), loanProductId,
AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue());
// income
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), loanProductId,
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_FEES.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(), loanProductId,
AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue());
// expenses
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId,
AccrualAccountsForLoan.GOODWILL_CREDIT.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), loanProductId,
AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), loanProductId,
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue());
// liabilities
this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element,
LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId, AccrualAccountsForLoan.OVERPAYMENT.getValue());
// advanced accounting mappings
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
break;
}
}
@Override
@Transactional
public void createSavingProductToGLAccountMapping(final Long savingProductId, final JsonCommand command,
DepositAccountType accountType) {
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final Integer accountingRuleTypeId = this.fromApiJsonHelper.extractIntegerNamed(accountingRuleParamName, element,
Locale.getDefault());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
// asset
this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), savingProductId,
CashAccountsForSavings.SAVINGS_REFERENCE.getValue());
if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingProductId,
CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue());
}
// income
this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), savingProductId,
CashAccountsForSavings.INCOME_FROM_FEES.getValue());
this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), savingProductId,
CashAccountsForSavings.INCOME_FROM_PENALTIES.getValue());
if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), savingProductId,
CashAccountsForSavings.INCOME_FROM_INTEREST.getValue());
}
// expenses
this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), savingProductId,
CashAccountsForSavings.INTEREST_ON_SAVINGS.getValue());
if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), savingProductId,
CashAccountsForSavings.LOSSES_WRITTEN_OFF.getValue());
}
// liability
this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), savingProductId,
CashAccountsForSavings.SAVINGS_CONTROL.getValue());
this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), savingProductId,
CashAccountsForSavings.TRANSFERS_SUSPENSE.getValue());
final Boolean isDormancyTrackingActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName,
element);
if (null != isDormancyTrackingActive && isDormancyTrackingActive) {
this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), savingProductId,
CashAccountsForSavings.ESCHEAT_LIABILITY.getValue());
}
// advanced accounting mappings
this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null);
this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null);
break;
default:
break;
}
}
@Override
@Transactional
public void createShareProductToGLAccountMapping(final Long shareProductId, final JsonCommand command) {
this.deserializer.validateForShareProductCreate(command.json());
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final Integer accountingRuleTypeId = this.fromApiJsonHelper.extractIntegerNamed(accountingRuleParamName, element,
Locale.getDefault());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
// asset
this.shareProductToGLAccountMappingHelper.saveSharesToAssetAccountMapping(element,
SharesProductAccountingParams.SHARES_REFERENCE.getValue(), shareProductId,
CashAccountsForShares.SHARES_REFERENCE.getValue());
// income
this.shareProductToGLAccountMappingHelper.saveSharesToIncomeAccountMapping(element,
SharesProductAccountingParams.INCOME_FROM_FEES.getValue(), shareProductId,
CashAccountsForShares.INCOME_FROM_FEES.getValue());
// expenses
this.shareProductToGLAccountMappingHelper.saveSharesToEquityAccountMapping(element,
SharesProductAccountingParams.SHARES_EQUITY.getValue(), shareProductId,
CashAccountsForShares.SHARES_EQUITY.getValue());
// liability
this.shareProductToGLAccountMappingHelper.saveSharesToLiabilityAccountMapping(element,
SharesProductAccountingParams.SHARES_SUSPENSE.getValue(), shareProductId,
CashAccountsForShares.SHARES_SUSPENSE.getValue());
// advanced accounting mappings
this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, shareProductId, null);
this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, shareProductId, null);
break;
default:
break;
}
}
@Override
@Transactional
public Map<String, Object> updateLoanProductToGLAccountMapping(final Long loanProductId, final JsonCommand command,
final boolean accountingRuleChanged, final int accountingRuleTypeId) {
/***
* Variable tracks all accounting mapping properties that have been updated
***/
Map<String, Object> changes = new HashMap<>();
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
/***
* If the accounting rule has been changed, delete all existing mapping for the product and recreate a new set
* of mappings
***/
if (accountingRuleChanged) {
this.deserializer.validateForLoanProductCreate(command.json());
this.loanProductToGLAccountMappingHelper.deleteLoanProductToGLAccountMapping(loanProductId);
createLoanProductToGLAccountMapping(loanProductId, command);
changes = this.loanProductToGLAccountMappingHelper.populateChangesForNewLoanProductToGLAccountMappingCreation(element,
accountingRuleType);
} /*** else examine and update individual changes ***/
else {
this.loanProductToGLAccountMappingHelper.handleChangesToLoanProductToGLAccountMappings(loanProductId, changes, element,
accountingRuleType);
this.loanProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, loanProductId, changes);
this.loanProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, loanProductId, changes);
}
return changes;
}
@Override
@Transactional
public Map<String, Object> updateSavingsProductToGLAccountMapping(final Long savingsProductId, final JsonCommand command,
final boolean accountingRuleChanged, final int accountingRuleTypeId, final DepositAccountType accountType) {
/***
* Variable tracks all accounting mapping properties that have been updated
***/
Map<String, Object> changes = new HashMap<>();
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
/***
* If the accounting rule has been changed, delete all existing mapping for the product and recreate a new set
* of mappings
***/
if (accountingRuleChanged) {
this.deserializer.validateForSavingsProductCreate(command.json(), accountType);
this.savingsProductToGLAccountMappingHelper.deleteSavingsProductToGLAccountMapping(savingsProductId);
createSavingProductToGLAccountMapping(savingsProductId, command, accountType);
changes = this.savingsProductToGLAccountMappingHelper.populateChangesForNewSavingsProductToGLAccountMappingCreation(element,
accountingRuleType);
} /*** else examine and update individual changes ***/
else {
this.savingsProductToGLAccountMappingHelper.handleChangesToSavingsProductToGLAccountMappings(savingsProductId, changes, element,
accountingRuleType);
this.savingsProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, savingsProductId,
changes);
this.savingsProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, savingsProductId, changes);
}
return changes;
}
@Override
@Transactional
public Map<String, Object> updateShareProductToGLAccountMapping(final Long shareProductId, final JsonCommand command,
final boolean accountingRuleChanged, final int accountingRuleTypeId) {
/***
* Variable tracks all accounting mapping properties that have been updated
***/
Map<String, Object> changes = new HashMap<>();
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
/***
* If the accounting rule has been changed, delete all existing mapping for the product and recreate a new set
* of mappings
***/
if (accountingRuleChanged) {
this.shareProductToGLAccountMappingHelper.deleteSharesProductToGLAccountMapping(shareProductId);
createShareProductToGLAccountMapping(shareProductId, command);
changes = this.shareProductToGLAccountMappingHelper.populateChangesForNewSharesProductToGLAccountMappingCreation(element,
accountingRuleType);
} /*** else examine and update individual changes ***/
else {
this.shareProductToGLAccountMappingHelper.handleChangesToSharesProductToGLAccountMappings(shareProductId, changes, element,
accountingRuleType);
this.shareProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, shareProductId, changes);
this.shareProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, shareProductId, changes);
}
return changes;
}
}
@@ -0,0 +1,241 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.service;
import com.google.gson.JsonElement;
import java.util.HashMap;
import java.util.Map;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
import org.springframework.stereotype.Component;
@Component
public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper {
public SavingsProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
final ChargeRepositoryWrapper chargeRepositoryWrapper, final GLAccountRepositoryWrapper accountRepositoryWrapper,
final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
paymentTypeRepositoryWrapper);
}
/***
* Set of abstractions for saving Saving Products to GL Account Mappings
***/
public void saveSavingsToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET, PortfolioProductType.SAVING);
}
public void saveSavingsToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME, PortfolioProductType.SAVING);
}
public void saveSavingsToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.SAVING);
}
public void saveSavingsToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.SAVING);
}
/***
* Set of abstractions for merging Savings Products to GL Account Mappings
***/
public void mergeSavingsToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET,
PortfolioProductType.SAVING);
}
public void mergeSavingsToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME,
PortfolioProductType.SAVING);
}
public void mergeSavingsToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE,
PortfolioProductType.SAVING);
}
public void mergeSavingsToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.LIABILITY,
PortfolioProductType.SAVING);
}
public void createOrmergeSavingsToLiabilityAccountMappingChanges(final JsonElement element, final String paramName,
final Long productId, final int accountTypeId, final Map<String, Object> changes) {
createOrmergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, changes, GLAccountType.LIABILITY,
PortfolioProductType.SAVING);
}
/*** Abstractions for payments channel related to savings products ***/
public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING);
}
public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING);
}
public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
// save both fee and penalty charges
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true);
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false);
}
public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
// update both fee and penalty charges
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true);
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false);
}
public Map<String, Object> populateChangesForNewSavingsProductToGLAccountMappingCreation(final JsonElement element,
final AccountingRuleType accountingRuleType) {
final Map<String, Object> changes = new HashMap<>();
final Long savingsReferenceId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(),
element);
final Long incomeFromFeesId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
element);
final Long incomeFromPenaltiesId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
final Long interestOnSavingsId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), element);
final Long savingsControlId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(),
element);
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
final Long overdraftControlId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
final Long incomeFromInterest = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element);
final Long writeOffId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
element);
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
changes.put(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), savingsControlId);
changes.put(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), savingsReferenceId);
changes.put(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), interestOnSavingsId);
changes.put(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), incomeFromFeesId);
changes.put(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltiesId);
changes.put(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
changes.put(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), overdraftControlId);
changes.put(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), incomeFromInterest);
changes.put(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), writeOffId);
break;
case ACCRUAL_PERIODIC:
break;
case ACCRUAL_UPFRONT:
break;
}
return changes;
}
/**
* Examines and updates each account mapping for given loan product with changes passed in from the Json element
*
* @param savingsProductId
* @param changes
* @param element
* @param accountingRuleType
*/
public void handleChangesToSavingsProductToGLAccountMappings(final Long savingsProductId, final Map<String, Object> changes,
final JsonElement element, final AccountingRuleType accountingRuleType) {
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
// asset
mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(),
savingsProductId, CashAccountsForSavings.SAVINGS_REFERENCE.getValue(),
CashAccountsForSavings.SAVINGS_REFERENCE.toString(), changes);
mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
savingsProductId, CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.toString(), changes);
// income
mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
savingsProductId, CashAccountsForSavings.INCOME_FROM_FEES.getValue(),
CashAccountsForSavings.INCOME_FROM_FEES.toString(), changes);
mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(),
savingsProductId, CashAccountsForSavings.INCOME_FROM_PENALTIES.getValue(),
CashAccountsForSavings.INCOME_FROM_PENALTIES.toString(), changes);
mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(),
savingsProductId, CashAccountsForSavings.INCOME_FROM_INTEREST.getValue(),
CashAccountsForSavings.INCOME_FROM_INTEREST.toString(), changes);
// expenses
mergeSavingsToExpenseAccountMappingChanges(element, SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(),
savingsProductId, CashAccountsForSavings.INTEREST_ON_SAVINGS.getValue(),
CashAccountsForSavings.INTEREST_ON_SAVINGS.toString(), changes);
mergeSavingsToExpenseAccountMappingChanges(element, SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
savingsProductId, CashAccountsForSavings.LOSSES_WRITTEN_OFF.getValue(),
CashAccountsForSavings.LOSSES_WRITTEN_OFF.toString(), changes);
// liability
mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.SAVINGS_CONTROL.getValue(),
savingsProductId, CashAccountsForSavings.SAVINGS_CONTROL.getValue(),
CashAccountsForSavings.SAVINGS_CONTROL.toString(), changes);
mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(),
savingsProductId, CashAccountsForSavings.TRANSFERS_SUSPENSE.getValue(),
CashAccountsForSavings.TRANSFERS_SUSPENSE.toString(), changes);
createOrmergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(),
savingsProductId, CashAccountsForSavings.ESCHEAT_LIABILITY.getValue(), changes);
break;
case ACCRUAL_PERIODIC:
break;
case ACCRUAL_UPFRONT:
break;
}
}
public void deleteSavingsProductToGLAccountMapping(final Long savingsProductId) {
deleteProductToGLAccountMapping(savingsProductId, PortfolioProductType.SAVING);
}
}
@@ -0,0 +1,196 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.producttoaccountmapping.service;
import com.google.gson.JsonElement;
import java.util.HashMap;
import java.util.Map;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares;
import org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
import org.springframework.stereotype.Component;
@Component
public class ShareProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper {
public ShareProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
final ChargeRepositoryWrapper chargeRepositoryWrapper, final GLAccountRepositoryWrapper accountRepositoryWrapper,
final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
paymentTypeRepositoryWrapper);
}
/***
* Set of abstractions for saving Share Products to GL Account Mappings
***/
public void saveSharesToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET, PortfolioProductType.SHARES);
}
public void saveSharesToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME, PortfolioProductType.SHARES);
}
public void saveSharesToEquityAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EQUITY, PortfolioProductType.SHARES);
}
public void saveSharesToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.SHARES);
}
/***
* Set of abstractions for merging Shares Products to GL Account Mappings
***/
public void mergeSharesToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET,
PortfolioProductType.SHARES);
}
public void mergeSharesToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME,
PortfolioProductType.SHARES);
}
public void mergeSharesToEquityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EQUITY,
PortfolioProductType.SHARES);
}
public void mergeSharesToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.LIABILITY,
PortfolioProductType.SHARES);
}
/*** Abstractions for payments channel related to Shares products ***/
public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SHARES);
}
public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SHARES);
}
public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SHARES, true);
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SHARES, false);
}
public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SHARES, true);
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SHARES, false);
}
public Map<String, Object> populateChangesForNewSharesProductToGLAccountMappingCreation(final JsonElement element,
final AccountingRuleType accountingRuleType) {
final Map<String, Object> changes = new HashMap<>();
final Long shareReferenceId = this.fromApiJsonHelper.extractLongNamed(SharesProductAccountingParams.SHARES_REFERENCE.getValue(),
element);
final Long incomeFromFeeAccountId = this.fromApiJsonHelper
.extractLongNamed(SharesProductAccountingParams.INCOME_FROM_FEES.getValue(), element);
final Long shareSuspenseId = this.fromApiJsonHelper.extractLongNamed(SharesProductAccountingParams.SHARES_SUSPENSE.getValue(),
element);
final Long shareEquityId = this.fromApiJsonHelper.extractLongNamed(SharesProductAccountingParams.SHARES_EQUITY.getValue(), element);
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
changes.put(SharesProductAccountingParams.SHARES_REFERENCE.getValue(), shareReferenceId);
changes.put(SharesProductAccountingParams.INCOME_FROM_FEES.getValue(), incomeFromFeeAccountId);
changes.put(SharesProductAccountingParams.SHARES_SUSPENSE.getValue(), shareSuspenseId);
changes.put(SharesProductAccountingParams.SHARES_EQUITY.getValue(), shareEquityId);
break;
case ACCRUAL_PERIODIC:
break;
case ACCRUAL_UPFRONT:
break;
}
return changes;
}
/**
* Examines and updates each account mapping for given loan product with changes passed in from the Json element
*
* @param sharesProductId
* @param changes
* @param element
* @param accountingRuleType
*/
public void handleChangesToSharesProductToGLAccountMappings(final Long sharesProductId, final Map<String, Object> changes,
final JsonElement element, final AccountingRuleType accountingRuleType) {
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
// asset
mergeSharesToAssetAccountMappingChanges(element, SharesProductAccountingParams.SHARES_REFERENCE.getValue(), sharesProductId,
CashAccountsForShares.SHARES_REFERENCE.getValue(), CashAccountsForShares.SHARES_REFERENCE.toString(), changes);
// income
mergeSharesToIncomeAccountMappingChanges(element, SharesProductAccountingParams.INCOME_FROM_FEES.getValue(),
sharesProductId, CashAccountsForShares.INCOME_FROM_FEES.getValue(),
CashAccountsForShares.INCOME_FROM_FEES.toString(), changes);
// liability
mergeSharesToLiabilityAccountMappingChanges(element, SharesProductAccountingParams.SHARES_SUSPENSE.getValue(),
sharesProductId, CashAccountsForShares.SHARES_SUSPENSE.getValue(), CashAccountsForShares.SHARES_SUSPENSE.toString(),
changes);
// equity
mergeSharesToEquityAccountMappingChanges(element, SharesProductAccountingParams.SHARES_EQUITY.getValue(), sharesProductId,
CashAccountsForShares.SHARES_EQUITY.getValue(), CashAccountsForShares.SHARES_EQUITY.toString(), changes);
break;
case ACCRUAL_PERIODIC:
break;
case ACCRUAL_UPFRONT:
break;
}
}
public void deleteSharesProductToGLAccountMapping(final Long sharesProductId) {
deleteProductToGLAccountMapping(sharesProductId, PortfolioProductType.SHARES);
}
}
@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.exceptions;
public class LoanAccountLockCannotBeOverruledException extends RuntimeException {
public LoanAccountLockCannotBeOverruledException(String message) {
super(message);
}
}
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exception;
import jakarta.annotation.PostConstruct;
import org.apache.fineract.batch.exception.ErrorHandler;
import org.apache.fineract.batch.exception.ErrorInfo;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDomainRuleExceptionMapper;
import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.apache.http.HttpStatus;
public class ErrorHandlerRegister {
@PostConstruct
public void init() {
ErrorHandler.registerNewErrorHandler(LoanIdsHardLockedException.class, runtimeException -> new ErrorInfo(HttpStatus.SC_CONFLICT,
4090,
ApiGlobalErrorResponse.loanIsLocked(((LoanIdsHardLockedException) runtimeException).getLoanIdFromRequest()).toJson()));
ErrorHandler.registerNewErrorHandler(LinkedAccountRequiredException.class, new PlatformDomainRuleExceptionMapper(), 3002);
ErrorHandler.registerNewErrorHandler(MultiDisbursementDataRequiredException.class, new PlatformDomainRuleExceptionMapper(), 3003);
}
}
@@ -0,0 +1,45 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exceptionmapper;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.springframework.stereotype.Component;
@Provider
@Component
@Slf4j
public class LoanAccountLockCannotBeOverruledExceptionMapper implements ExceptionMapper<LoanAccountLockCannotBeOverruledException> {
@Override
public Response toResponse(LoanAccountLockCannotBeOverruledException exception) {
final String globalisationMessageCode = "error.msg.invalid.request.body";
final String defaultUserMessage = exception.getMessage();
log.warn("Exception: {}, Message: {}", exception.getClass().getName(), defaultUserMessage);
final ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage);
return Response.status(Response.Status.CONFLICT).entity(error).type(MediaType.APPLICATION_JSON).build();
}
}
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.event.business.domain.journalentry;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
import org.apache.fineract.infrastructure.event.business.domain.NoExternalEvent;
public class LoanJournalEntryCreatedBusinessEvent extends JournalEntryBusinessEvent implements NoExternalEvent {
private static final String TYPE = "JournalEntryCreatedBusinessEvent";
public LoanJournalEntryCreatedBusinessEvent(JournalEntry value) {
super(value);
}
@Override
public String getType() {
return TYPE;
}
@Override
public Long getAggregateRootId() {
return get().getLoanTransactionId();
}
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.jobs.exception;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class LoanIdsHardLockedException extends RuntimeException {
private final Long loanIdFromRequest;
}
@@ -0,0 +1,40 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.data;
import java.io.Serializable;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class LoanScheduleDelinquencyData implements Serializable {
private Long loanId;
private LocalDate overdueSinceDate;
private Long overdueDays;
private Loan loan;
}
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal;
import java.util.Objects;
import lombok.Getter;
/**
* Immutable data object representing the total unpaid charge details
*/
@Getter
public class UnpaidChargeData {
private static final String NULL_VALUE_IS_NOT_ALLOWED = "Null value is not allowed";
private final Long chargeId;
private final String chargeName;
private final BigDecimal outstandingAmount;
public UnpaidChargeData(Long chargeId, String chargeName, BigDecimal outstandingAmount) {
this.chargeId = Objects.requireNonNull(chargeId, NULL_VALUE_IS_NOT_ALLOWED);
this.chargeName = Objects.requireNonNull(chargeName, NULL_VALUE_IS_NOT_ALLOWED);
this.outstandingAmount = Objects.requireNonNull(outstandingAmount, NULL_VALUE_IS_NOT_ALLOWED);
}
}
@@ -0,0 +1,73 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.domain;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface LoanTransactionRepository extends JpaRepository<LoanTransaction, Long>, JpaSpecificationExecutor<LoanTransaction> {
String FIND_ID_BY_EXTERNAL_ID = "SELECT lt.id FROM LoanTransaction lt WHERE lt.externalId = :externalId";
String FIND_LOAN_ID_BY_ID = "SELECT lt.loan.id FROM LoanTransaction lt WHERE lt.id = :id";
Optional<LoanTransaction> findByIdAndLoanId(Long transactionId, Long loanId);
@Query("""
SELECT new org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData(
lt.loan.id,
min(lt.dateOf),
0L,
lt.loan
) FROM LoanTransaction lt
WHERE lt.typeOf = :transactionType and
lt.dateOf <= :businessDate and
lt.loan.loanProduct.delinquencyBucket is not null
GROUP BY lt.loan
""")
Collection<LoanScheduleDelinquencyData> fetchLoanTransactionsByTypeAndLessOrEqualDate(@Param("transactionType") Integer transactionType,
@Param("businessDate") LocalDate businessDate);
@Query(FIND_ID_BY_EXTERNAL_ID)
Long findIdByExternalId(@Param("externalId") ExternalId externalId);
@Query("""
SELECT new org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData(
lc.charge.id,
lc.charge.name,
SUM(lc.amountOutstanding)
) FROM LoanCharge lc
WHERE lc.loan = :loan
AND lc.active = true
AND lc.amountOutstanding > 0
GROUP BY lc.charge.id, lc.charge.name
""")
List<UnpaidChargeData> fetchTotalUnpaidChargesForLoan(@Param("loan") Loan loan);
@Query(FIND_LOAN_ID_BY_ID)
Optional<Long> findLoanIdById(@Param("id") Long id);
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanproduct.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LinkedAccountRequiredException extends AbstractPlatformDomainRuleException {
public LinkedAccountRequiredException(final String entity, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".requires.linked.account", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -31,6 +31,7 @@
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- Fineract core module -->
<class>org.apache.fineract.accounting.glaccount.domain.GLAccount</class>
<class>org.apache.fineract.accounting.journalentry.domain.JournalEntry</class>
<class>org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom</class>
<class>org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom</class>
<class>org.apache.fineract.infrastructure.codes.domain.Code</class>