| /** |
| * 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.journalentry.service; |
| |
| import java.math.BigDecimal; |
| import java.time.LocalDate; |
| import java.util.ArrayList; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import lombok.RequiredArgsConstructor; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.fineract.accounting.closure.domain.GLClosure; |
| import org.apache.fineract.accounting.closure.domain.GLClosureRepository; |
| import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan; |
| import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings; |
| 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.FinancialActivity; |
| import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount; |
| import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper; |
| import org.apache.fineract.accounting.glaccount.domain.GLAccount; |
| import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository; |
| import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO; |
| import org.apache.fineract.accounting.journalentry.data.ClientChargePaymentDTO; |
| import org.apache.fineract.accounting.journalentry.data.ClientTransactionDTO; |
| import org.apache.fineract.accounting.journalentry.data.LoanDTO; |
| import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO; |
| import org.apache.fineract.accounting.journalentry.data.SavingsDTO; |
| import org.apache.fineract.accounting.journalentry.data.SavingsTransactionDTO; |
| import org.apache.fineract.accounting.journalentry.data.SharesDTO; |
| import org.apache.fineract.accounting.journalentry.data.SharesTransactionDTO; |
| import org.apache.fineract.accounting.journalentry.data.TaxPaymentDTO; |
| import org.apache.fineract.accounting.journalentry.domain.JournalEntry; |
| import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; |
| import org.apache.fineract.accounting.journalentry.domain.JournalEntryType; |
| import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException; |
| import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException.GlJournalEntryInvalidReason; |
| 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.ProductToGLAccountMappingNotFoundException; |
| import org.apache.fineract.infrastructure.core.data.EnumOptionData; |
| import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; |
| import org.apache.fineract.infrastructure.core.service.DateUtils; |
| import org.apache.fineract.infrastructure.event.business.domain.journalentry.LoanJournalEntryCreatedBusinessEvent; |
| import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; |
| import org.apache.fineract.organisation.office.domain.Office; |
| import org.apache.fineract.organisation.office.domain.OfficeRepository; |
| import org.apache.fineract.portfolio.account.PortfolioAccountType; |
| import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; |
| import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; |
| import org.apache.fineract.portfolio.client.domain.ClientTransaction; |
| import org.apache.fineract.portfolio.client.domain.ClientTransactionRepository; |
| import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; |
| import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData; |
| import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; |
| import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; |
| import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; |
| import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; |
| import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; |
| import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionEnumData; |
| import org.springframework.dao.DataAccessException; |
| |
| @RequiredArgsConstructor |
| public class AccountingProcessorHelper { |
| |
| public static final String LOAN_TRANSACTION_IDENTIFIER = "L"; |
| public static final String SAVINGS_TRANSACTION_IDENTIFIER = "S"; |
| public static final String CLIENT_TRANSACTION_IDENTIFIER = "C"; |
| public static final String PROVISIONING_TRANSACTION_IDENTIFIER = "P"; |
| public static final String SHARE_TRANSACTION_IDENTIFIER = "SH"; |
| |
| private final JournalEntryRepository glJournalEntryRepository; |
| private final ProductToGLAccountMappingRepository accountMappingRepository; |
| private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository; |
| private final GLClosureRepository closureRepository; |
| private final GLAccountRepository glAccountRepository; |
| private final OfficeRepository officeRepository; |
| private final LoanTransactionRepository loanTransactionRepository; |
| private final ClientTransactionRepository clientTransactionRepository; |
| private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; |
| private final AccountTransfersReadPlatformService accountTransfersReadPlatformService; |
| private final ChargeRepositoryWrapper chargeRepositoryWrapper; |
| private final BusinessEventNotifierService businessEventNotifierService; |
| |
| public LoanDTO populateLoanDtoFromMap(final Map<String, Object> accountingBridgeData, final boolean cashBasedAccountingEnabled, |
| final boolean upfrontAccrualBasedAccountingEnabled, final boolean periodicAccrualBasedAccountingEnabled) { |
| final Long loanId = (Long) accountingBridgeData.get("loanId"); |
| final Long loanProductId = (Long) accountingBridgeData.get("loanProductId"); |
| final Long officeId = (Long) accountingBridgeData.get("officeId"); |
| final String currencyCode = (String) accountingBridgeData.get("currencyCode"); |
| final List<LoanTransactionDTO> newLoanTransactions = new ArrayList<>(); |
| boolean isAccountTransfer = (Boolean) accountingBridgeData.get("isAccountTransfer"); |
| boolean isLoanMarkedAsChargeOff = (Boolean) accountingBridgeData.get("isChargeOff"); |
| boolean isLoanMarkedAsFraud = (Boolean) accountingBridgeData.get("isFraud"); |
| |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> newTransactionsMap = (List<Map<String, Object>>) accountingBridgeData.get("newLoanTransactions"); |
| |
| for (final Map<String, Object> map : newTransactionsMap) { |
| final Long transactionOfficeId = (Long) map.get("officeId"); |
| final String transactionId = ((Long) map.get("id")).toString(); |
| final LocalDate transactionDate = ((LocalDate) map.get("date")); |
| final LoanTransactionEnumData transactionType = (LoanTransactionEnumData) map.get("type"); |
| final BigDecimal amount = (BigDecimal) map.get("amount"); |
| final BigDecimal principal = (BigDecimal) map.get("principalPortion"); |
| final BigDecimal interest = (BigDecimal) map.get("interestPortion"); |
| final BigDecimal fees = (BigDecimal) map.get("feeChargesPortion"); |
| final BigDecimal penalties = (BigDecimal) map.get("penaltyChargesPortion"); |
| final BigDecimal overPayments = (BigDecimal) map.get("overPaymentPortion"); |
| final boolean reversed = (Boolean) map.get("reversed"); |
| final Long paymentTypeId = (Long) map.get("paymentTypeId"); |
| final String chargeRefundChargeType = (String) map.get("chargeRefundChargeType"); |
| final LoanChargeData loanChargeData = (LoanChargeData) map.get("loanChargeData"); |
| |
| final List<ChargePaymentDTO> feePaymentDetails = new ArrayList<>(); |
| final List<ChargePaymentDTO> penaltyPaymentDetails = new ArrayList<>(); |
| // extract charge payment details (if exists) |
| if (map.containsKey("loanChargesPaid")) { |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> loanChargesPaidData = (List<Map<String, Object>>) map.get("loanChargesPaid"); |
| for (final Map<String, Object> loanChargePaid : loanChargesPaidData) { |
| final Long chargeId = (Long) loanChargePaid.get("chargeId"); |
| final Long loanChargeId = (Long) loanChargePaid.get("loanChargeId"); |
| final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty"); |
| final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); |
| final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, chargeAmountPaid, loanChargeId); |
| if (isPenalty) { |
| penaltyPaymentDetails.add(chargePaymentDTO); |
| } else { |
| feePaymentDetails.add(chargePaymentDTO); |
| } |
| } |
| } |
| |
| if (!isAccountTransfer) { |
| isAccountTransfer = this.accountTransfersReadPlatformService.isAccountTransfer(Long.parseLong(transactionId), |
| PortfolioAccountType.LOAN); |
| } |
| |
| BigDecimal principalPaid = (BigDecimal) map.get("principalPaid"); |
| BigDecimal feePaid = (BigDecimal) map.get("feePaid"); |
| BigDecimal penaltyPaid = (BigDecimal) map.get("penaltyPaid"); |
| |
| final LoanTransactionDTO transaction = new LoanTransactionDTO(transactionOfficeId, paymentTypeId, transactionId, |
| transactionDate, transactionType, amount, principal, interest, fees, penalties, overPayments, reversed, |
| penaltyPaymentDetails, feePaymentDetails, isAccountTransfer, chargeRefundChargeType, loanChargeData, principalPaid, |
| feePaid, penaltyPaid); |
| Boolean isLoanToLoanTransfer = (Boolean) accountingBridgeData.get("isLoanToLoanTransfer"); |
| transaction.setLoanToLoanTransfer(isLoanToLoanTransfer != null && isLoanToLoanTransfer); |
| newLoanTransactions.add(transaction); |
| |
| } |
| |
| return new LoanDTO(loanId, loanProductId, officeId, currencyCode, cashBasedAccountingEnabled, upfrontAccrualBasedAccountingEnabled, |
| periodicAccrualBasedAccountingEnabled, newLoanTransactions, isLoanMarkedAsChargeOff, isLoanMarkedAsFraud); |
| } |
| |
| public SavingsDTO populateSavingsDtoFromMap(final Map<String, Object> accountingBridgeData, final boolean cashBasedAccountingEnabled, |
| final boolean accrualBasedAccountingEnabled) { |
| final Long loanId = (Long) accountingBridgeData.get("savingsId"); |
| final Long loanProductId = (Long) accountingBridgeData.get("savingsProductId"); |
| final Long officeId = (Long) accountingBridgeData.get("officeId"); |
| final String currencyCode = (String) accountingBridgeData.get("currencyCode"); |
| final List<SavingsTransactionDTO> newSavingsTransactions = new ArrayList<>(); |
| boolean isAccountTransfer = (Boolean) accountingBridgeData.get("isAccountTransfer"); |
| |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> newTransactionsMap = (List<Map<String, Object>>) accountingBridgeData.get("newSavingsTransactions"); |
| |
| for (final Map<String, Object> map : newTransactionsMap) { |
| final Long transactionOfficeId = (Long) map.get("officeId"); |
| final String transactionId = ((Long) map.get("id")).toString(); |
| final LocalDate transactionDate = ((LocalDate) map.get("date")); |
| final SavingsAccountTransactionEnumData transactionType = (SavingsAccountTransactionEnumData) map.get("type"); |
| final BigDecimal amount = (BigDecimal) map.get("amount"); |
| final boolean reversed = (Boolean) map.get("reversed"); |
| final Long paymentTypeId = (Long) map.get("paymentTypeId"); |
| final BigDecimal overdraftAmount = (BigDecimal) map.get("overdraftAmount"); |
| |
| final List<ChargePaymentDTO> feePayments = new ArrayList<>(); |
| final List<ChargePaymentDTO> penaltyPayments = new ArrayList<>(); |
| // extract charge payment details (if exists) |
| if (map.containsKey("savingsChargesPaid")) { |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> savingsChargesPaidData = (List<Map<String, Object>>) map.get("savingsChargesPaid"); |
| for (final Map<String, Object> loanChargePaid : savingsChargesPaidData) { |
| final Long chargeId = (Long) loanChargePaid.get("chargeId"); |
| final Long loanChargeId = (Long) loanChargePaid.get("savingsChargeId"); |
| final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty"); |
| final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); |
| final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, chargeAmountPaid, loanChargeId); |
| if (isPenalty) { |
| penaltyPayments.add(chargePaymentDTO); |
| } else { |
| feePayments.add(chargePaymentDTO); |
| } |
| } |
| } |
| |
| final List<TaxPaymentDTO> taxPayments = new ArrayList<>(); |
| if (map.containsKey("taxDetails")) { |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> taxDataList = (List<Map<String, Object>>) map.get("taxDetails"); |
| for (final Map<String, Object> taxData : taxDataList) { |
| final BigDecimal taxAmount = (BigDecimal) taxData.get("amount"); |
| final Long creditAccountId = (Long) taxData.get("creditAccountId"); |
| final Long debitAccountId = (Long) taxData.get("debitAccountId"); |
| taxPayments.add(new TaxPaymentDTO(debitAccountId, creditAccountId, taxAmount)); |
| } |
| } |
| |
| if (!isAccountTransfer) { |
| isAccountTransfer = this.accountTransfersReadPlatformService.isAccountTransfer(Long.parseLong(transactionId), |
| PortfolioAccountType.SAVINGS); |
| } |
| final SavingsTransactionDTO transaction = new SavingsTransactionDTO(transactionOfficeId, paymentTypeId, transactionId, |
| transactionDate, transactionType, amount, reversed, feePayments, penaltyPayments, overdraftAmount, isAccountTransfer, |
| taxPayments); |
| |
| newSavingsTransactions.add(transaction); |
| |
| } |
| |
| return new SavingsDTO(loanId, loanProductId, officeId, currencyCode, cashBasedAccountingEnabled, accrualBasedAccountingEnabled, |
| newSavingsTransactions); |
| } |
| |
| public SharesDTO populateSharesDtoFromMap(final Map<String, Object> accountingBridgeData, final boolean cashBasedAccountingEnabled, |
| final boolean accrualBasedAccountingEnabled) { |
| final Long shareAccountId = (Long) accountingBridgeData.get("shareAccountId"); |
| final Long shareProductId = (Long) accountingBridgeData.get("shareProductId"); |
| final Long officeId = (Long) accountingBridgeData.get("officeId"); |
| final String currencyCode = (String) accountingBridgeData.get("currencyCode"); |
| final List<SharesTransactionDTO> newTransactions = new ArrayList<>(); |
| |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> newTransactionsMap = (List<Map<String, Object>>) accountingBridgeData.get("newTransactions"); |
| |
| for (final Map<String, Object> map : newTransactionsMap) { |
| final Long transactionOfficeId = (Long) map.get("officeId"); |
| final String transactionId = ((Long) map.get("id")).toString(); |
| final LocalDate transactionDate = ((LocalDate) map.get("date")); |
| final ShareAccountTransactionEnumData transactionType = (ShareAccountTransactionEnumData) map.get("type"); |
| final ShareAccountTransactionEnumData transactionStatus = (ShareAccountTransactionEnumData) map.get("status"); |
| final BigDecimal amount = (BigDecimal) map.get("amount"); |
| final BigDecimal chargeAmount = (BigDecimal) map.get("chargeAmount"); |
| final Long paymentTypeId = (Long) map.get("paymentTypeId"); |
| |
| final List<ChargePaymentDTO> feePayments = new ArrayList<>(); |
| // extract charge payment details (if exists) |
| if (map.containsKey("chargesPaid")) { |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> chargesPaidData = (List<Map<String, Object>>) map.get("chargesPaid"); |
| for (final Map<String, Object> chargePaid : chargesPaidData) { |
| final Long chargeId = (Long) chargePaid.get("chargeId"); |
| final Long loanChargeId = (Long) chargePaid.get("sharesChargeId"); |
| final BigDecimal chargeAmountPaid = (BigDecimal) chargePaid.get("amount"); |
| final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, chargeAmountPaid, loanChargeId); |
| feePayments.add(chargePaymentDTO); |
| } |
| } |
| final SharesTransactionDTO transaction = new SharesTransactionDTO(transactionOfficeId, paymentTypeId, transactionId, |
| transactionDate, transactionType, transactionStatus, amount, chargeAmount, feePayments); |
| |
| newTransactions.add(transaction); |
| |
| } |
| |
| return new SharesDTO(shareAccountId, shareProductId, officeId, currencyCode, cashBasedAccountingEnabled, |
| accrualBasedAccountingEnabled, newTransactions); |
| } |
| |
| public ClientTransactionDTO populateClientTransactionDtoFromMap(final Map<String, Object> accountingBridgeData) { |
| |
| final Long transactionOfficeId = (Long) accountingBridgeData.get("officeId"); |
| final Long clientId = (Long) accountingBridgeData.get("clientId"); |
| final Long transactionId = (Long) accountingBridgeData.get("id"); |
| final LocalDate transactionDate = ((LocalDate) accountingBridgeData.get("date")); |
| final EnumOptionData transactionType = (EnumOptionData) accountingBridgeData.get("type"); |
| final BigDecimal amount = (BigDecimal) accountingBridgeData.get("amount"); |
| final boolean reversed = (Boolean) accountingBridgeData.get("reversed"); |
| final Long paymentTypeId = (Long) accountingBridgeData.get("paymentTypeId"); |
| final String currencyCode = (String) accountingBridgeData.get("currencyCode"); |
| final Boolean accountingEnabled = (Boolean) accountingBridgeData.get("accountingEnabled"); |
| |
| final List<ClientChargePaymentDTO> clientChargePaymentDTOs = new ArrayList<>(); |
| // extract client charge payment details (if exists) |
| if (accountingBridgeData.containsKey("clientChargesPaid")) { |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> clientChargesPaidData = (List<Map<String, Object>>) accountingBridgeData |
| .get("clientChargesPaid"); |
| for (final Map<String, Object> clientChargePaid : clientChargesPaidData) { |
| final Long chargeId = (Long) clientChargePaid.get("chargeId"); |
| final Long clientChargeId = (Long) clientChargePaid.get("clientChargeId"); |
| final boolean isPenalty = (Boolean) clientChargePaid.get("isPenalty"); |
| final BigDecimal chargeAmountPaid = (BigDecimal) clientChargePaid.get("amount"); |
| final Long incomeAccountId = (Long) clientChargePaid.get("incomeAccountId"); |
| final ClientChargePaymentDTO clientChargePaymentDTO = new ClientChargePaymentDTO(chargeId, chargeAmountPaid, clientChargeId, |
| isPenalty, incomeAccountId); |
| clientChargePaymentDTOs.add(clientChargePaymentDTO); |
| } |
| } |
| |
| return new ClientTransactionDTO(clientId, transactionOfficeId, paymentTypeId, transactionId, transactionDate, transactionType, |
| currencyCode, amount, reversed, accountingEnabled, clientChargePaymentDTOs); |
| |
| } |
| |
| /** |
| * Convenience method that creates a pair of related Debits and Credits for Accrual Based accounting. |
| * |
| * The target accounts for debits and credits are switched in case of a reversal |
| * |
| * @param office |
| * office |
| * @param currencyCode |
| * currencyCode |
| * @param accountTypeToBeDebited |
| * Enum of the placeholder GLAccount to be debited |
| * @param accountTypeToBeCredited |
| * Enum of the placeholder of the GLAccount to be credited |
| * @param loanProductId |
| * loanProductId |
| * @param loanId |
| * loanId |
| * @param transactionId |
| * transactionId |
| * @param transactionDate |
| * transactionDate |
| * @param totalAmount |
| * totalAmount |
| * @param isReversal |
| * isReversal |
| * @param chargePaymentDTOs |
| * chargePaymentDTOs |
| */ |
| public void createJournalEntriesAndReversalsForLoanCharges(final Office office, final String currencyCode, |
| final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long loanId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal, |
| final List<ChargePaymentDTO> chargePaymentDTOs) { |
| |
| GLAccount receivableAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountTypeToBeDebited, null); |
| final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>(); |
| for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) { |
| final Long chargeId = chargePaymentDTO.getChargeId(); |
| final GLAccount chargeSpecificAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountTypeToBeCredited, chargeId); |
| BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount(); |
| |
| // adjust net credit amount if the account is already present in the |
| // map |
| if (creditDetailsMap.containsKey(chargeSpecificAccount)) { |
| final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount); |
| chargeSpecificAmount = chargeSpecificAmount.add(existingAmount); |
| } |
| creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount); |
| } |
| |
| BigDecimal totalCreditedAmount = BigDecimal.ZERO; |
| for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) { |
| final GLAccount account = entry.getKey(); |
| final BigDecimal amount = entry.getValue(); |
| totalCreditedAmount = totalCreditedAmount.add(amount); |
| if (isReversal) { |
| createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| createCreditJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount); |
| } else { |
| createDebitJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount); |
| createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| if (totalAmount.compareTo(totalCreditedAmount) != 0) { |
| throw new PlatformDataIntegrityException( |
| "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction", |
| "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction", |
| totalCreditedAmount, totalAmount); |
| } |
| } |
| |
| /** |
| * Convenience method that creates a pair of related Debits and Credits for Cash Based accounting. |
| * |
| * The target accounts for debits and credits are switched in case of a reversal |
| * |
| * @param office |
| * @param accountTypeToBeDebited |
| * Enum of the placeholder GLAccount to be debited |
| * @param accountTypeToBeCredited |
| * Enum of the placeholder of the GLAccount to be credited |
| * @param savingsProductId |
| * @param paymentTypeId |
| * @param loanId |
| * @param transactionId |
| * @param transactionDate |
| * @param amount |
| * @param isReversal |
| */ |
| public void createCashBasedJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long savingsProductId, |
| final Long paymentTypeId, final Long loanId, final String transactionId, final LocalDate transactionDate, |
| final BigDecimal amount, final Boolean isReversal) { |
| int accountTypeToDebitId = accountTypeToBeDebited; |
| int accountTypeToCreditId = accountTypeToBeCredited; |
| // reverse debits and credits for reversals |
| if (isReversal) { |
| accountTypeToDebitId = accountTypeToBeCredited; |
| accountTypeToCreditId = accountTypeToBeDebited; |
| } |
| createJournalEntriesForSavings(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, savingsProductId, paymentTypeId, |
| loanId, transactionId, transactionDate, amount); |
| } |
| |
| /** |
| * Convenience method that creates a pair of related Debits and Credits for Cash Based accounting. |
| * |
| * The target accounts for debits and credits are switched in case of a reversal |
| * |
| * @param office |
| * @param accountTypeToBeDebited |
| * Enum of the placeholder GLAccount to be debited |
| * @param accountTypeToBeCredited |
| * Enum of the placeholder of the GLAccount to be credited |
| * @param loanProductId |
| * @param paymentTypeId |
| * @param loanId |
| * @param transactionId |
| * @param transactionDate |
| * @param amount |
| * @param isReversal |
| */ |
| public void createJournalEntriesAndReversalsForLoan(final Office office, final String currencyCode, |
| final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId, |
| final Long loanId, final String transactionId, final LocalDate transactionDate, final BigDecimal amount, |
| final Boolean isReversal) { |
| int accountTypeToDebitId = accountTypeToBeDebited; |
| int accountTypeToCreditId = accountTypeToBeCredited; |
| // reverse debits and credits for reversals |
| if (isReversal) { |
| accountTypeToDebitId = accountTypeToBeCredited; |
| accountTypeToCreditId = accountTypeToBeDebited; |
| } |
| createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, loanId, |
| transactionId, transactionDate, amount); |
| } |
| |
| public void createSplitJournalEntriesAndReversalsForLoan(Office office, String currencyCode, |
| List<JournalAmountHolder> splitAccountsHolder, JournalAmountHolder totalAccountHolder, Long loanProductId, Long paymentTypeId, |
| Long loanId, String transactionId, LocalDate transactionDate, Boolean isReversal) { |
| splitAccountsHolder.forEach(journalItemHolder -> { |
| final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, journalItemHolder.getAccountType(), paymentTypeId); |
| if (isReversal) { |
| createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, |
| journalItemHolder.getAmount()); |
| } else { |
| createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, |
| journalItemHolder.getAmount()); |
| } |
| |
| }); |
| final GLAccount totalAccount = getLinkedGLAccountForLoanProduct(loanProductId, totalAccountHolder.getAccountType(), paymentTypeId); |
| if (isReversal) { |
| createDebitJournalEntryForLoan(office, currencyCode, totalAccount, loanId, transactionId, transactionDate, |
| totalAccountHolder.getAmount()); |
| } else { |
| createCreditJournalEntryForLoan(office, currencyCode, totalAccount, loanId, transactionId, transactionDate, |
| totalAccountHolder.getAmount()); |
| } |
| } |
| |
| public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, |
| final CashAccountsForLoan accountMappingType, final Long loanProductId, final Long paymentTypeId, final Long loanId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| final int accountMappingTypeId = accountMappingType.getValue(); |
| createCreditJournalEntryOrReversalForLoan(office, currencyCode, accountMappingTypeId, loanProductId, paymentTypeId, loanId, |
| transactionId, transactionDate, amount, isReversal); |
| } |
| |
| public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, |
| final AccrualAccountsForLoan accountMappingType, final Long loanProductId, final Long paymentTypeId, final Long loanId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| final int accountMappingTypeId = accountMappingType.getValue(); |
| createCreditJournalEntryOrReversalForLoan(office, currencyCode, accountMappingTypeId, loanProductId, paymentTypeId, loanId, |
| transactionId, transactionDate, amount, isReversal); |
| } |
| |
| /** |
| * @param latestGLClosure |
| * @param transactionDate |
| */ |
| public void checkForBranchClosures(final GLClosure latestGLClosure, final LocalDate transactionDate) { |
| // check if an accounting closure has happened for this branch after the transaction Date |
| if (latestGLClosure != null) { |
| if (!DateUtils.isBefore(latestGLClosure.getClosingDate(), transactionDate)) { |
| throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), |
| null, null); |
| } |
| } |
| } |
| |
| public GLClosure getLatestClosureByBranch(final long officeId) { |
| return this.closureRepository.getLatestGLClosureByBranch(officeId); |
| } |
| |
| private void createJournalEntriesForLoan(final Office office, final String currencyCode, final int accountTypeToDebitId, |
| final int accountTypeToCreditId, final Long loanProductId, final Long paymentTypeId, final Long loanId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final GLAccount debitAccount = getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToDebitId, paymentTypeId); |
| final GLAccount creditAccount = getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToCreditId, paymentTypeId); |
| createDebitJournalEntryForLoan(office, currencyCode, debitAccount, loanId, transactionId, transactionDate, amount); |
| createCreditJournalEntryForLoan(office, currencyCode, creditAccount, loanId, transactionId, transactionDate, amount); |
| } |
| |
| private void createJournalEntriesForSavings(final Office office, final String currencyCode, final int accountTypeToDebitId, |
| final int accountTypeToCreditId, final Long savingsProductId, final Long paymentTypeId, final Long savingsId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final GLAccount debitAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToDebitId, paymentTypeId); |
| final GLAccount creditAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToCreditId, paymentTypeId); |
| createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); |
| createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); |
| } |
| |
| /** |
| * Convenience method that creates a pair of related Debits and Credits for Cash Based accounting. |
| * |
| * The target accounts for debits and credits are switched in case of a reversal |
| * |
| * @param office |
| * @param currencyCode |
| * @param accountTypeToBeDebited |
| * Enum of the placeholder GLAccount to be debited |
| * @param accountTypeToBeCredited |
| * Enum of the placeholder of the GLAccount to be credited |
| * @param savingsProductId |
| * @param paymentTypeId |
| * @param savingsId |
| * @param transactionId |
| * @param transactionDate |
| * @param amount |
| * @param isReversal |
| * @param taxDetails |
| */ |
| public void createCashBasedJournalEntriesAndReversalsForSavingsTax(final Office office, final String currencyCode, |
| final CashAccountsForSavings accountTypeToBeDebited, final CashAccountsForSavings accountTypeToBeCredited, |
| final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal, final List<TaxPaymentDTO> taxDetails) { |
| |
| for (TaxPaymentDTO taxPaymentDTO : taxDetails) { |
| if (taxPaymentDTO.getAmount() != null) { |
| if (taxPaymentDTO.getCreditAccountId() == null) { |
| createCashBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode, accountTypeToBeCredited.getValue(), |
| savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, taxPaymentDTO.getAmount(), |
| isReversal); |
| } else { |
| createCashBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode, taxPaymentDTO.getCreditAccountId(), |
| savingsId, transactionId, transactionDate, taxPaymentDTO.getAmount(), isReversal); |
| } |
| } |
| } |
| createCashBasedDebitJournalEntriesAndReversalsForSavings(office, currencyCode, accountTypeToBeDebited.getValue(), savingsProductId, |
| paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); |
| } |
| |
| public void createAccrualBasedJournalEntriesAndReversalsForSavingsTax(final Office office, final String currencyCode, |
| final AccrualAccountsForSavings accountTypeToBeDebited, final AccrualAccountsForSavings accountTypeToBeCredited, |
| final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal, final List<TaxPaymentDTO> taxDetails) { |
| |
| for (TaxPaymentDTO taxPaymentDTO : taxDetails) { |
| if (taxPaymentDTO.getAmount() != null) { |
| if (taxPaymentDTO.getCreditAccountId() == null) { |
| createAccrualBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode, accountTypeToBeCredited.getValue(), |
| savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, taxPaymentDTO.getAmount(), |
| isReversal); |
| } else { |
| createAccrualBasedBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode, |
| taxPaymentDTO.getCreditAccountId(), savingsId, transactionId, transactionDate, taxPaymentDTO.getAmount(), |
| isReversal); |
| } |
| } |
| } |
| createAccrualBasedDebitJournalEntriesAndReversalsForSavings(office, currencyCode, accountTypeToBeDebited.getValue(), |
| savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); |
| } |
| |
| public void createCashBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Integer accountTypeToBeDebited, final Long savingsProductId, final Long paymentTypeId, final Long savingsId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| // reverse debits and credits for reversals |
| if (isReversal) { |
| createCreditJournalEntriesForSavings(office, currencyCode, accountTypeToBeDebited, savingsProductId, paymentTypeId, savingsId, |
| transactionId, transactionDate, amount); |
| } else { |
| createDebitJournalEntriesForSavings(office, currencyCode, accountTypeToBeDebited, savingsProductId, paymentTypeId, savingsId, |
| transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createCashBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Integer accountTypeToBeCredited, final Long savingsProductId, final Long paymentTypeId, final Long savingsId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| // reverse debits and credits for reversals |
| if (isReversal) { |
| createDebitJournalEntriesForSavings(office, currencyCode, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId, |
| transactionId, transactionDate, amount); |
| } else { |
| createCreditJournalEntriesForSavings(office, currencyCode, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId, |
| transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createCashBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Long debitAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate, |
| final BigDecimal amount, final Boolean isReversal) { |
| // reverse debits and credits for reversals |
| final GLAccount debitAccount = getGLAccountById(debitAccountId); |
| if (isReversal) { |
| createCreditJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); |
| } else { |
| createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createCashBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Long creditAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate, |
| final BigDecimal amount, final Boolean isReversal) { |
| // reverse debits and credits for reversals |
| final GLAccount creditAccount = getGLAccountById(creditAccountId); |
| if (isReversal) { |
| createDebitJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); |
| } else { |
| createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createAccrualBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Integer accountTypeToBeDebited, final Long savingsProductId, final Long paymentTypeId, final Long savingsId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| // reverse debits and credits for reversals |
| if (isReversal) { |
| createCreditJournalEntriesForSavings(office, currencyCode, accountTypeToBeDebited, savingsProductId, paymentTypeId, savingsId, |
| transactionId, transactionDate, amount); |
| } else { |
| createDebitJournalEntriesForSavings(office, currencyCode, accountTypeToBeDebited, savingsProductId, paymentTypeId, savingsId, |
| transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createAccrualBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Integer accountTypeToBeCredited, final Long savingsProductId, final Long paymentTypeId, final Long savingsId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| // reverse debits and credits for reversals |
| if (isReversal) { |
| createDebitJournalEntriesForSavings(office, currencyCode, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId, |
| transactionId, transactionDate, amount); |
| } else { |
| createCreditJournalEntriesForSavings(office, currencyCode, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId, |
| transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createAccrualBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Long debitAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate, |
| final BigDecimal amount, final Boolean isReversal) { |
| // reverse debits and credits for reversals |
| final GLAccount debitAccount = getGLAccountById(debitAccountId); |
| if (isReversal) { |
| createCreditJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); |
| } else { |
| createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createAccrualBasedBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, |
| final Long creditAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate, |
| final BigDecimal amount, final Boolean isReversal) { |
| // reverse debits and credits for reversals |
| final GLAccount creditAccount = getGLAccountById(creditAccountId); |
| if (isReversal) { |
| createDebitJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); |
| } else { |
| createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| private void createDebitJournalEntriesForSavings(final Office office, final String currencyCode, final int accountTypeToDebitId, |
| final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount) { |
| final GLAccount debitAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToDebitId, paymentTypeId); |
| createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); |
| } |
| |
| private void createCreditJournalEntriesForSavings(final Office office, final String currencyCode, final int accountTypeToCreditId, |
| final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount) { |
| final GLAccount creditAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToCreditId, paymentTypeId); |
| createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); |
| } |
| |
| public void createDebitJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId, |
| final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId, paymentTypeId); |
| if (isReversal) { |
| createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } else { |
| createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createDebitJournalEntryOrReversalForLoanCharges(final Office office, final String currencyCode, |
| final int accountMappingTypeId, final Long loanProductId, final Long chargeId, final Long loanId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| final GLAccount account = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId); |
| if (isReversal) { |
| createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } else { |
| createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| public void createCreditJournalEntryOrReversalForLoanCharges(final Office office, final String currencyCode, |
| final int accountMappingTypeId, final Long loanProductId, final Long loanId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal, |
| final List<ChargePaymentDTO> chargePaymentDTOs) { |
| /*** |
| * Map to track each account and the net credit to be made for a particular account |
| ***/ |
| final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>(); |
| for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) { |
| final Long chargeId = chargePaymentDTO.getChargeId(); |
| final GLAccount chargeSpecificAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId); |
| BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount(); |
| |
| // adjust net credit amount if the account is already present in the |
| // map |
| if (creditDetailsMap.containsKey(chargeSpecificAccount)) { |
| final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount); |
| chargeSpecificAmount = chargeSpecificAmount.add(existingAmount); |
| } |
| creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount); |
| } |
| |
| BigDecimal totalCreditedAmount = BigDecimal.ZERO; |
| for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) { |
| final GLAccount account = entry.getKey(); |
| final BigDecimal amount = entry.getValue(); |
| totalCreditedAmount = totalCreditedAmount.add(amount); |
| if (isReversal) { |
| createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } else { |
| createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| // TODO: Vishwas Temporary validation to be removed before moving to |
| // release branch |
| if (totalAmount.compareTo(totalCreditedAmount) != 0) { |
| throw new PlatformDataIntegrityException( |
| "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction", |
| "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction", |
| totalCreditedAmount, totalAmount); |
| } |
| } |
| |
| /** |
| * Convenience method that creates a pair of related Debits and Credits for Cash Based accounting. |
| * |
| * The target accounts for debits and credits are switched in case of a reversal |
| * |
| * @param office |
| * office |
| * @param currencyCode |
| * currencyCode |
| * @param accountTypeToBeDebited |
| * Enum of the placeholder GLAccount to be debited |
| * @param accountTypeToBeCredited |
| * Enum of the placeholder of the GLAccount to be credited |
| * @param savingsProductId |
| * savingsProductId |
| * @param paymentTypeId |
| * paymentTypeId |
| * @param loanId |
| * loanId |
| * @param transactionId |
| * transactionId |
| * @param transactionDate |
| * transactionDate |
| * @param totalAmount |
| * totalAmount |
| * @param isReversal |
| * isReversal |
| * @param chargePaymentDTOs |
| * chargePaymentDTOs |
| */ |
| public void createCashBasedJournalEntriesAndReversalsForSavingsCharges(final Office office, final String currencyCode, |
| final CashAccountsForSavings accountTypeToBeDebited, CashAccountsForSavings accountTypeToBeCredited, |
| final Long savingsProductId, final Long paymentTypeId, final Long loanId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal, |
| final List<ChargePaymentDTO> chargePaymentDTOs) { |
| // TODO Vishwas: Remove this validation, as and when appropriate Junit |
| // tests are written for accounting |
| /** |
| * Accounting module currently supports a single charge per transaction, throw an error if this is not the case |
| * here so any developers changing the expected portfolio behavior would also take care of modifying the |
| * accounting code appropriately |
| **/ |
| if (chargePaymentDTOs.size() != 1) { |
| throw new PlatformDataIntegrityException("Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code", |
| "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code"); |
| } |
| ChargePaymentDTO chargePaymentDTO = chargePaymentDTOs.get(0); |
| GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(), |
| chargePaymentDTO.getChargeId()); |
| |
| final GLAccount savingsControlAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToBeDebited.getValue(), |
| paymentTypeId); |
| if (isReversal) { |
| createDebitJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, |
| totalAmount); |
| createCreditJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, |
| totalAmount); |
| } else { |
| createDebitJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, |
| totalAmount); |
| createCreditJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, |
| totalAmount); |
| } |
| } |
| |
| public void createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(final Office office, final String currencyCode, |
| final AccrualAccountsForSavings accountTypeToBeDebited, AccrualAccountsForSavings accountTypeToBeCredited, |
| final Long savingsProductId, final Long paymentTypeId, final Long loanId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal, |
| final List<ChargePaymentDTO> chargePaymentDTOs) { |
| // TODO Vishwas: Remove this validation, as and when appropriate Junit |
| // tests are written for accounting |
| /** |
| * Accounting module currently supports a single charge per transaction, throw an error if this is not the case |
| * here so any developers changing the expected portfolio behavior would also take care of modifying the |
| * accounting code appropriately |
| **/ |
| if (chargePaymentDTOs.size() != 1) { |
| throw new PlatformDataIntegrityException("Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code", |
| "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code"); |
| } |
| ChargePaymentDTO chargePaymentDTO = chargePaymentDTOs.get(0); |
| GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(), |
| chargePaymentDTO.getChargeId()); |
| |
| final GLAccount savingsControlAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToBeDebited.getValue(), |
| paymentTypeId); |
| if (isReversal) { |
| createDebitJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, |
| totalAmount); |
| createCreditJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, |
| totalAmount); |
| } else { |
| createDebitJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, |
| totalAmount); |
| createCreditJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, |
| totalAmount); |
| } |
| } |
| |
| public LoanTransaction getLoanTransactionById(final Long loanTransactionId) { |
| return this.loanTransactionRepository.getReferenceById(loanTransactionId); |
| } |
| |
| public SavingsAccountTransaction getSavingsTransactionById(final Long savingsTransactionId) { |
| return this.savingsAccountTransactionRepository.getReferenceById(savingsTransactionId); |
| } |
| |
| public ClientTransaction getClientTransactionById(final Long clientTransactionId) { |
| return this.clientTransactionRepository.getReferenceById(clientTransactionId); |
| } |
| |
| public Office getOfficeById(final long officeId) { |
| return this.officeRepository.getReferenceById(officeId); |
| } |
| |
| public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId, |
| final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) { |
| final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId, paymentTypeId); |
| createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate, amount, isReversal, |
| account); |
| } |
| |
| public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final Long loanId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal, |
| final GLAccount account) { |
| if (isReversal) { |
| createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } else { |
| createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| private void createCreditJournalEntryForClientPayments(final Office office, final String currencyCode, final GLAccount account, |
| final Long clientId, final Long transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final boolean manualEntry = false; |
| |
| String modifiedTransactionId = CLIENT_TRANSACTION_IDENTIFIER + transactionId; |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.CLIENT.getValue(), clientId, null, null, null, |
| transactionId, null); |
| persistJournalEntry(journalEntry); |
| } |
| |
| private void createCreditJournalEntryForSavings(final Office office, final String currencyCode, final GLAccount account, |
| final Long savingsId, final String transactionId, final LocalDate transactionDate, final BigDecimal amount) |
| throws DataAccessException { |
| final boolean manualEntry = false; |
| Long savingsAccountTransactionId = null; |
| String modifiedTransactionId = transactionId; |
| if (StringUtils.isNumeric(transactionId)) { |
| savingsAccountTransactionId = Long.parseLong(transactionId); |
| modifiedTransactionId = SAVINGS_TRANSACTION_IDENTIFIER + transactionId; |
| } |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, null, |
| savingsAccountTransactionId, null, null); |
| |
| persistJournalEntry(journalEntry); |
| } |
| |
| private void createCreditJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, final Long loanId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final boolean manualEntry = false; |
| Long loanTransactionId = null; |
| String modifiedTransactionId = transactionId; |
| if (StringUtils.isNumeric(transactionId)) { |
| loanTransactionId = Long.parseLong(transactionId); |
| modifiedTransactionId = LOAN_TRANSACTION_IDENTIFIER + transactionId; |
| } |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId, null, |
| loanTransactionId, null, null, null); |
| persistJournalEntry(journalEntry); |
| } |
| |
| public void createProvisioningDebitJournalEntry(LocalDate transactionDate, Long provisioningEntryId, Office office, String currencyCode, |
| GLAccount account, BigDecimal amount) { |
| final boolean manualEntry = false; |
| String modifiedTransactionId = PROVISIONING_TRANSACTION_IDENTIFIER + provisioningEntryId; |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.PROVISIONING.getValue(), provisioningEntryId, |
| null, null, null, null, null); |
| persistJournalEntry(journalEntry); |
| } |
| |
| public void createProvisioningCreditJournalEntry(LocalDate transactionDate, Long provisioningEntryId, Office office, |
| String currencyCode, GLAccount account, BigDecimal amount) { |
| final boolean manualEntry = false; |
| String modifiedTransactionId = PROVISIONING_TRANSACTION_IDENTIFIER + provisioningEntryId; |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.PROVISIONING.getValue(), provisioningEntryId, |
| null, null, null, null, null); |
| persistJournalEntry(journalEntry); |
| } |
| |
| private void createDebitJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, final Long loanId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final boolean manualEntry = false; |
| Long loanTransactionId = null; |
| String modifiedTransactionId = transactionId; |
| if (StringUtils.isNumeric(transactionId)) { |
| loanTransactionId = Long.parseLong(transactionId); |
| modifiedTransactionId = LOAN_TRANSACTION_IDENTIFIER + transactionId; |
| } |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId, null, |
| loanTransactionId, null, null, null); |
| persistJournalEntry(journalEntry); |
| } |
| |
| private void createDebitJournalEntryForSavings(final Office office, final String currencyCode, final GLAccount account, |
| final Long savingsId, final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final boolean manualEntry = false; |
| Long savingsAccountTransactionId = null; |
| String modifiedTransactionId = transactionId; |
| if (StringUtils.isNumeric(transactionId)) { |
| savingsAccountTransactionId = Long.parseLong(transactionId); |
| modifiedTransactionId = SAVINGS_TRANSACTION_IDENTIFIER + transactionId; |
| } |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, null, |
| savingsAccountTransactionId, null, null); |
| |
| persistJournalEntry(journalEntry); |
| } |
| |
| private void createDebitJournalEntryForClientPayments(final Office office, final String currencyCode, final GLAccount account, |
| final Long clientId, final Long transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final boolean manualEntry = false; |
| String modifiedTransactionId = CLIENT_TRANSACTION_IDENTIFIER + transactionId; |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.CLIENT.getValue(), clientId, null, null, null, |
| transactionId, null); |
| persistJournalEntry(journalEntry); |
| } |
| |
| public void createJournalEntriesForShares(final Office office, final String currencyCode, final int accountTypeToDebitId, |
| final int accountTypeToCreditId, final Long shareProductId, final Long paymentTypeId, final Long shareAccountId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| createDebitJournalEntryForShares(office, currencyCode, accountTypeToDebitId, shareProductId, paymentTypeId, shareAccountId, |
| transactionId, transactionDate, amount); |
| createCreditJournalEntryForShares(office, currencyCode, accountTypeToCreditId, shareProductId, paymentTypeId, shareAccountId, |
| transactionId, transactionDate, amount); |
| } |
| |
| public void createDebitJournalEntryForShares(final Office office, final String currencyCode, final int accountTypeToDebitId, |
| final Long shareProductId, final Long paymentTypeId, final Long shareAccountId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount) { |
| final GLAccount debitAccount = getLinkedGLAccountForShareProduct(shareProductId, accountTypeToDebitId, paymentTypeId); |
| createDebitJournalEntryForShares(office, currencyCode, debitAccount, shareAccountId, transactionId, transactionDate, amount); |
| } |
| |
| public void createCreditJournalEntryForShares(final Office office, final String currencyCode, final int accountTypeToCreditId, |
| final Long shareProductId, final Long paymentTypeId, final Long shareAccountId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal amount) { |
| final GLAccount creditAccount = getLinkedGLAccountForShareProduct(shareProductId, accountTypeToCreditId, paymentTypeId); |
| createCreditJournalEntryForShares(office, currencyCode, creditAccount, shareAccountId, transactionId, transactionDate, amount); |
| } |
| |
| public void createCashBasedJournalEntriesForSharesCharges(final Office office, final String currencyCode, |
| final CashAccountsForShares accountTypeToBeDebited, final CashAccountsForShares accountTypeToBeCredited, |
| final Long shareProductId, final Long paymentTypeId, final Long shareAccountId, final String transactionId, |
| final LocalDate transactionDate, final BigDecimal totalAmount, final List<ChargePaymentDTO> chargePaymentDTOs) { |
| |
| createDebitJournalEntryForShares(office, currencyCode, accountTypeToBeDebited.getValue(), shareProductId, paymentTypeId, |
| shareAccountId, transactionId, transactionDate, totalAmount); |
| createCashBasedJournalEntryForSharesCharges(office, currencyCode, accountTypeToBeCredited, shareProductId, shareAccountId, |
| transactionId, transactionDate, totalAmount, chargePaymentDTOs); |
| } |
| |
| public void createCashBasedJournalEntryForSharesCharges(final Office office, final String currencyCode, |
| final CashAccountsForShares accountTypeToBeCredited, final Long shareProductId, final Long shareAccountId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal totalAmount, |
| final List<ChargePaymentDTO> chargePaymentDTOs) { |
| final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>(); |
| for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) { |
| final GLAccount chargeSpecificAccount = getLinkedGLAccountForShareCharges(shareProductId, accountTypeToBeCredited.getValue(), |
| chargePaymentDTO.getChargeId()); |
| BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount(); |
| |
| // adjust net credit amount if the account is already present in the |
| // map |
| if (creditDetailsMap.containsKey(chargeSpecificAccount)) { |
| final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount); |
| chargeSpecificAmount = chargeSpecificAmount.add(existingAmount); |
| } |
| creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount); |
| } |
| |
| BigDecimal totalCreditedAmount = BigDecimal.ZERO; |
| for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) { |
| final GLAccount account = entry.getKey(); |
| final BigDecimal amount = entry.getValue(); |
| totalCreditedAmount = totalCreditedAmount.add(amount); |
| createCreditJournalEntryForShares(office, currencyCode, account, shareAccountId, transactionId, transactionDate, amount); |
| } |
| if (totalAmount.compareTo(totalCreditedAmount) != 0) { |
| throw new PlatformDataIntegrityException("Recent Portfolio changes w.r.t Charges for shares have Broken the accounting code", |
| "Recent Portfolio changes w.r.t Charges for shares have Broken the accounting code"); |
| } |
| } |
| |
| public void revertCashBasedJournalEntryForSharesCharges(final Office office, final String currencyCode, |
| final CashAccountsForShares accountTypeToBeCredited, final Long shareProductId, final Long shareAccountId, |
| final String transactionId, final LocalDate transactionDate, final BigDecimal totalAmount, |
| final List<ChargePaymentDTO> chargePaymentDTOs) { |
| final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>(); |
| for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) { |
| final GLAccount chargeSpecificAccount = getLinkedGLAccountForShareCharges(shareProductId, accountTypeToBeCredited.getValue(), |
| chargePaymentDTO.getChargeId()); |
| BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount(); |
| |
| // adjust net credit amount if the account is already present in the |
| // map |
| if (creditDetailsMap.containsKey(chargeSpecificAccount)) { |
| final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount); |
| chargeSpecificAmount = chargeSpecificAmount.add(existingAmount); |
| } |
| creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount); |
| } |
| |
| BigDecimal totalCreditedAmount = BigDecimal.ZERO; |
| for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) { |
| final GLAccount account = entry.getKey(); |
| final BigDecimal amount = entry.getValue(); |
| totalCreditedAmount = totalCreditedAmount.add(amount); |
| createDebitJournalEntryForShares(office, currencyCode, account, shareAccountId, transactionId, transactionDate, amount); |
| } |
| if (totalAmount.compareTo(totalCreditedAmount) != 0) { |
| throw new PlatformDataIntegrityException("Recent Portfolio changes w.r.t Charges for shares have Broken the accounting code", |
| "Recent Portfolio changes w.r.t Charges for shares have Broken the accounting code"); |
| } |
| } |
| |
| private void createDebitJournalEntryForShares(final Office office, final String currencyCode, final GLAccount account, |
| final Long shareAccountId, final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final boolean manualEntry = false; |
| Long shareTransactionId = null; |
| String modifiedTransactionId = transactionId; |
| if (StringUtils.isNumeric(transactionId)) { |
| shareTransactionId = Long.parseLong(transactionId); |
| modifiedTransactionId = SHARE_TRANSACTION_IDENTIFIER + transactionId; |
| } |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SHARES.getValue(), shareAccountId, null, null, |
| null, null, shareTransactionId); |
| persistJournalEntry(journalEntry); |
| } |
| |
| private void createCreditJournalEntryForShares(final Office office, final String currencyCode, final GLAccount account, |
| final Long shareAccountId, final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { |
| final boolean manualEntry = false; |
| Long shareTransactionId = null; |
| String modifiedTransactionId = transactionId; |
| if (StringUtils.isNumeric(transactionId)) { |
| shareTransactionId = Long.parseLong(transactionId); |
| modifiedTransactionId = SHARE_TRANSACTION_IDENTIFIER + transactionId; |
| } |
| final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, |
| transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.SHARES.getValue(), shareAccountId, null, null, |
| null, null, shareTransactionId); |
| persistJournalEntry(journalEntry); |
| } |
| |
| public GLAccount getLinkedGLAccountForLoanProduct(final Long loanProductId, final int accountMappingTypeId, final Long paymentTypeId) { |
| GLAccount glAccount; |
| if (isOrganizationAccount(accountMappingTypeId)) { |
| FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository |
| .findByFinancialActivityTypeWithNotFoundDetection(accountMappingTypeId); |
| glAccount = financialActivityAccount.getGlAccount(); |
| } else { |
| ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId, |
| PortfolioProductType.LOAN.getValue(), accountMappingTypeId); |
| |
| /**** |
| * Get more specific mapping for FUND source accounts (based on payment channels). Note that fund source |
| * placeholder ID would be same for both cash and accrual accounts |
| ***/ |
| if (accountMappingTypeId == CashAccountsForLoan.FUND_SOURCE.getValue()) { |
| final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository |
| .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(loanProductId, |
| PortfolioProductType.LOAN.getValue(), accountMappingTypeId, paymentTypeId); |
| if (paymentChannelSpecificAccountMapping != null) { |
| accountMapping = paymentChannelSpecificAccountMapping; |
| } |
| } |
| |
| if (accountMapping == null) { |
| throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.LOAN, loanProductId, |
| AccrualAccountsForLoan.fromInt(accountMappingTypeId).toString()); |
| |
| } |
| glAccount = accountMapping.getGlAccount(); |
| } |
| return glAccount; |
| } |
| |
| private GLAccount getLinkedGLAccountForLoanCharges(final Long loanProductId, final int accountMappingTypeId, final Long chargeId) { |
| ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId, |
| PortfolioProductType.LOAN.getValue(), accountMappingTypeId); |
| /***** |
| * Get more specific mappings for Charges and penalties (based on the actual charge /penalty coupled with the |
| * loan product). Note the income from fees and income from penalties placeholder ID would be the same for both |
| * cash and accrual based accounts |
| *****/ |
| |
| // Vishwas TODO: remove this condition as it should always be true |
| if (accountMappingTypeId == CashAccountsForLoan.INCOME_FROM_FEES.getValue() |
| || accountMappingTypeId == CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue()) { |
| final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository |
| .findProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(loanProductId, PortfolioProductType.LOAN.getValue(), |
| accountMappingTypeId, chargeId); |
| if (chargeSpecificIncomeAccountMapping != null) { |
| accountMapping = chargeSpecificIncomeAccountMapping; |
| } |
| } |
| return accountMapping.getGlAccount(); |
| } |
| |
| private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductId, final int accountMappingTypeId, |
| final Long chargeId) { |
| |
| ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, |
| PortfolioProductType.SAVING.getValue(), accountMappingTypeId); |
| /***** |
| * Get more specific mappings for Charges and penalties (based on the actual charge /penalty coupled with the |
| * loan product). Note the income from fees and income from penalties placeholder ID would be the same for both |
| * cash and accrual based accounts |
| *****/ |
| |
| // Vishwas TODO: remove this condition as it should always be true |
| |
| if (accountMappingTypeId == CashAccountsForSavings.INCOME_FROM_FEES.getValue() |
| || accountMappingTypeId == CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue()) { |
| GLAccount glAccount = chargeRepositoryWrapper.findOneWithNotFoundDetection(chargeId).getAccount(); |
| if (glAccount != null) { |
| return glAccount; |
| } |
| final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository |
| .findProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(savingsProductId, PortfolioProductType.SAVING.getValue(), |
| accountMappingTypeId, chargeId); |
| if (chargeSpecificIncomeAccountMapping != null) { |
| |
| accountMapping = chargeSpecificIncomeAccountMapping; |
| } |
| } |
| |
| return accountMapping.getGlAccount(); |
| } |
| |
| private GLAccount getLinkedGLAccountForSavingsProduct(final Long savingsProductId, final int accountMappingTypeId, |
| final Long paymentTypeId) { |
| GLAccount glAccount; |
| if (isOrganizationAccount(accountMappingTypeId)) { |
| FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository |
| .findByFinancialActivityTypeWithNotFoundDetection(accountMappingTypeId); |
| glAccount = financialActivityAccount.getGlAccount(); |
| } else { |
| ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, |
| PortfolioProductType.SAVING.getValue(), accountMappingTypeId); |
| /**** |
| * Get more specific mapping for FUND source accounts (based on payment channels). Note that fund source |
| * placeholder ID would be same for both cash and accrual accounts |
| ***/ |
| if (accountMappingTypeId == CashAccountsForSavings.SAVINGS_REFERENCE.getValue()) { |
| final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository |
| .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(savingsProductId, |
| PortfolioProductType.SAVING.getValue(), accountMappingTypeId, paymentTypeId); |
| if (paymentChannelSpecificAccountMapping != null) { |
| accountMapping = paymentChannelSpecificAccountMapping; |
| } |
| } |
| glAccount = accountMapping.getGlAccount(); |
| } |
| return glAccount; |
| } |
| |
| private GLAccount getLinkedGLAccountForShareProduct(final Long shareProductId, final int accountMappingTypeId, |
| final Long paymentTypeId) { |
| GLAccount glAccount; |
| if (isOrganizationAccount(accountMappingTypeId)) { |
| FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository |
| .findByFinancialActivityTypeWithNotFoundDetection(accountMappingTypeId); |
| glAccount = financialActivityAccount.getGlAccount(); |
| } else { |
| ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(shareProductId, |
| PortfolioProductType.SHARES.getValue(), accountMappingTypeId); |
| |
| if (accountMappingTypeId == CashAccountsForShares.SHARES_REFERENCE.getValue()) { |
| final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository |
| .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(shareProductId, |
| PortfolioProductType.SHARES.getValue(), accountMappingTypeId, paymentTypeId); |
| if (paymentChannelSpecificAccountMapping != null) { |
| accountMapping = paymentChannelSpecificAccountMapping; |
| } |
| } |
| glAccount = accountMapping.getGlAccount(); |
| } |
| return glAccount; |
| } |
| |
| private GLAccount getLinkedGLAccountForShareCharges(final Long shareProductId, final int accountMappingTypeId, final Long chargeId) { |
| ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(shareProductId, |
| PortfolioProductType.SHARES.getValue(), accountMappingTypeId); |
| /***** |
| * Get more specific mappings for Charges and penalties (based on the actual charge /penalty coupled with the |
| * loan product). Note the income from fees and income from penalties placeholder ID would be the same for both |
| * cash and accrual based accounts |
| *****/ |
| |
| final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository |
| .findProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(shareProductId, PortfolioProductType.SHARES.getValue(), |
| accountMappingTypeId, chargeId); |
| if (chargeSpecificIncomeAccountMapping != null) { |
| accountMapping = chargeSpecificIncomeAccountMapping; |
| } |
| return accountMapping.getGlAccount(); |
| } |
| |
| private boolean isOrganizationAccount(final int accountMappingTypeId) { |
| return FinancialActivity.fromInt(accountMappingTypeId) != null; |
| } |
| |
| public BigDecimal createCreditJournalEntryOrReversalForClientPayments(final Office office, final String currencyCode, |
| final Long clientId, final Long transactionId, final LocalDate transactionDate, final Boolean isReversal, |
| final List<ClientChargePaymentDTO> clientChargePaymentDTOs) { |
| /*** |
| * Map to track each account affected and the net credit to be made for a particular account |
| ***/ |
| final Map<Long, BigDecimal> creditDetailsMap = new LinkedHashMap<>(); |
| for (final ClientChargePaymentDTO clientChargePaymentDTO : clientChargePaymentDTOs) { |
| if (clientChargePaymentDTO.getIncomeAccountId() != null) { |
| final Long accountId = clientChargePaymentDTO.getIncomeAccountId(); |
| BigDecimal chargeSpecificAmount = clientChargePaymentDTO.getAmount(); |
| |
| // adjust net credit amount if the account is already present in |
| // the map |
| if (creditDetailsMap.containsKey(accountId)) { |
| final BigDecimal existingAmount = creditDetailsMap.get(accountId); |
| chargeSpecificAmount = chargeSpecificAmount.add(existingAmount); |
| } |
| creditDetailsMap.put(accountId, chargeSpecificAmount); |
| } |
| } |
| |
| BigDecimal totalCreditedAmount = BigDecimal.ZERO; |
| for (final Map.Entry<Long, BigDecimal> entry : creditDetailsMap.entrySet()) { |
| final GLAccount account = getGLAccountById(entry.getKey()); |
| final BigDecimal amount = entry.getValue(); |
| totalCreditedAmount = totalCreditedAmount.add(amount); |
| if (isReversal) { |
| createDebitJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); |
| } else { |
| createCreditJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); |
| } |
| } |
| return totalCreditedAmount; |
| } |
| |
| public void createDebitJournalEntryOrReversalForClientChargePayments(final Office office, final String currencyCode, |
| final Long clientId, final Long transactionId, final LocalDate transactionDate, final BigDecimal amount, |
| final Boolean isReversal) { |
| final GLAccount account = financialActivityAccountRepository |
| .findByFinancialActivityTypeWithNotFoundDetection(FinancialActivity.ASSET_FUND_SOURCE.getValue()).getGlAccount(); |
| if (isReversal) { |
| createCreditJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); |
| } else { |
| createDebitJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); |
| } |
| } |
| |
| private GLAccount getGLAccountById(final Long accountId) { |
| return this.glAccountRepository.getReferenceById(accountId); |
| } |
| |
| public Integer getValueForFeeOrPenaltyIncomeAccount(final String chargeRefundChargeType) { |
| if (chargeRefundChargeType == null |
| || !(chargeRefundChargeType.equalsIgnoreCase("P") || chargeRefundChargeType.equalsIgnoreCase("F"))) { |
| String errorValue; |
| errorValue = Objects.requireNonNullElse(chargeRefundChargeType, "Null"); |
| throw new PlatformDataIntegrityException("error.msg.chargeRefundChargeType.can.only.be.P.or.F", |
| "chargeRefundChargeType can only be P (Penalty) or F(Fee) - Value is: " + errorValue); |
| } |
| Integer incomeAccount; |
| if (chargeRefundChargeType.equalsIgnoreCase("P")) { |
| incomeAccount = AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(); |
| } else { |
| incomeAccount = AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(); |
| |
| } |
| return incomeAccount; |
| } |
| |
| public JournalEntry persistJournalEntry(JournalEntry journalEntry) { |
| boolean isNew = journalEntry.isNew(); |
| JournalEntry savedJournalEntry = this.glJournalEntryRepository.saveAndFlush(journalEntry); |
| if (isNew && journalEntry.getLoanTransactionId() != null) { |
| businessEventNotifierService.notifyPostBusinessEvent(new LoanJournalEntryCreatedBusinessEvent(savedJournalEntry)); |
| } |
| return savedJournalEntry; |
| } |
| |
| } |