diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/JournalEntryMapper.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/JournalEntryMapper.java index 30f6ac22b..1f35e6c48 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/JournalEntryMapper.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/JournalEntryMapper.java @@ -71,6 +71,7 @@ public interface JournalEntryMapper { @Mapping(target = "debits", ignore = true) @Mapping(target = "transactionDetails", ignore = true) @Mapping(target = "savingTransactionId", ignore = true) + @Mapping(target = "externalAssetOwner", ignore = true) JournalEntryData map(JournalEntry journalEntry); @Named("entityType") diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntryJsonInputParams.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntryJsonInputParams.java index 2dc5e9366..95b74783c 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntryJsonInputParams.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntryJsonInputParams.java @@ -43,7 +43,8 @@ public enum JournalEntryJsonInputParams { CHECK_NUMBER("checkNumber"), // ROUTING_CODE("routingCode"), // RECEIPT_NUMBER("receiptNumber"), // - BANK_NUMBER("bankNumber"); // + BANK_NUMBER("bankNumber"), // + EXTERNAL_ASSET_OWNER("externalAssetOwner"); // private final String value; diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/command/JournalEntryCommand.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/command/JournalEntryCommand.java index a50b809d9..45177d8b1 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/command/JournalEntryCommand.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/command/JournalEntryCommand.java @@ -54,6 +54,7 @@ public class JournalEntryCommand { private final SingleDebitOrCreditEntryCommand[] debits; private final String locale; private final String dateFormat; + private final String externalAssetOwner; public void validateForCreate() { @@ -75,6 +76,9 @@ public class JournalEntryCommand { baseDataValidator.reset().parameter("paymentTypeId").value(this.paymentTypeId).ignoreIfNull().longGreaterThanZero(); + baseDataValidator.reset().parameter(JournalEntryJsonInputParams.EXTERNAL_ASSET_OWNER.getValue()).value(this.externalAssetOwner) + .ignoreIfNull().notExceedingLengthOf(100); + // validation for credit array elements if (this.credits != null) { if (this.credits.length == 0) { diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java index c22e09616..db3197312 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java @@ -94,6 +94,7 @@ public class JournalEntryData { private String routingCode; private String receiptNumber; private String bankNumber; + private String externalAssetOwner; private transient Long savingTransactionId; public JournalEntryData() {} @@ -191,7 +192,7 @@ public class JournalEntryData { final EnumOptionData entityType, final Long entityId, final Long createdByUserId, final LocalDate submittedOnDate, final String createdByUserName, final String comments, final Boolean reversed, final String referenceNumber, final BigDecimal officeRunningBalance, final BigDecimal organizationRunningBalance, final Boolean runningBalanceComputed, - final TransactionDetailData transactionDetailData, final CurrencyData currency) { + final TransactionDetailData transactionDetailData, final CurrencyData currency, final String externalAssetOwner) { this.id = id; this.officeId = officeId; this.officeName = officeName; @@ -218,6 +219,7 @@ public class JournalEntryData { this.runningBalanceComputed = runningBalanceComputed; this.transactionDetails = transactionDetailData; this.currency = currency; + this.externalAssetOwner = externalAssetOwner; } public static JournalEntryData importInstance(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId, @@ -259,10 +261,11 @@ public class JournalEntryData { final Boolean runningBalanceComputed = null; final TransactionDetailData transactionDetailData = null; final CurrencyData currency = null; + final String externalAssetOwner = null; return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glAccountCode, glAccountClassification, transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate, createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance, - runningBalanceComputed, transactionDetailData, currency); + runningBalanceComputed, transactionDetailData, currency, externalAssetOwner); } public Integer getRowIndex() { @@ -274,7 +277,6 @@ public class JournalEntryData { } public void addDebits(CreditDebit debit) { - this.debits.add(debit); } diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java index 725225c91..3acc08691 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/journalentry/serialization/JournalEntryCommandFromApiJsonDeserializer.java @@ -84,6 +84,8 @@ public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFr element); final String bankNumber = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.BANK_NUMBER.getValue(), element); final String routingCode = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.ROUTING_CODE.getValue(), element); + final String externalAssetOwner = this.fromApiJsonHelper + .extractStringNamed(JournalEntryJsonInputParams.EXTERNAL_ASSET_OWNER.getValue(), element); SingleDebitOrCreditEntryCommand[] credits = null; SingleDebitOrCreditEntryCommand[] debits = null; @@ -99,7 +101,8 @@ public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFr } String dateFormat = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.DATE_FORMAT.getValue(), element); return new JournalEntryCommand(officeId, currencyCode, transactionDate, comments, referenceNumber, accountingRuleId, amount, - paymentTypeId, accountNumber, checkNumber, receiptNumber, bankNumber, routingCode, credits, debits, localeStr, dateFormat); + paymentTypeId, accountNumber, checkNumber, receiptNumber, bankNumber, routingCode, credits, debits, localeStr, dateFormat, + externalAssetOwner); } /** diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerNotFoundException.java b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerNotFoundException.java new file mode 100644 index 000000000..67f8094a6 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerNotFoundException.java @@ -0,0 +1,34 @@ +/** + * 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.investor.exception; + +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class ExternalAssetOwnerNotFoundException extends AbstractPlatformResourceNotFoundException { + + public ExternalAssetOwnerNotFoundException(ExternalId externalId) { + super("error.msg.external.asset.owner.external.id", + String.format("External asset owner with external id: %s does not found", externalId.getValue()), externalId.getValue()); + } + + public ExternalAssetOwnerNotFoundException(Long id) { + super("error.msg.external.asset.owner.id", String.format("External asset owner with id: %s does not found", id), id); + } +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/AccountingService.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/AccountingService.java index 08f5615fa..e7a1e892a 100755 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/AccountingService.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/AccountingService.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.investor.service; +import org.apache.fineract.accounting.journalentry.domain.JournalEntry; import org.apache.fineract.investor.domain.ExternalAssetOwner; import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -27,4 +28,6 @@ public interface AccountingService { void createJournalEntriesForSaleAssetTransfer(Loan loan, ExternalAssetOwnerTransfer transfer, ExternalAssetOwner previousOwner); void createJournalEntriesForBuybackAssetTransfer(Loan loan, ExternalAssetOwnerTransfer transfer); + + void createMappingToOwner(ExternalAssetOwner owner, JournalEntry journalEntry); } diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/AccountingServiceImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/AccountingServiceImpl.java index 80425abc7..98dcc2273 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/AccountingServiceImpl.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/AccountingServiceImpl.java @@ -120,7 +120,8 @@ public class AccountingServiceImpl implements AccountingService { return journalEntryList; } - private void createMappingToOwner(final ExternalAssetOwner owner, final JournalEntry journalEntry) { + @Override + public void createMappingToOwner(final ExternalAssetOwner owner, final JournalEntry journalEntry) { if (owner == null) { return; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java index cc0aa3b0c..2e15e99b2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java @@ -177,6 +177,8 @@ final class JournalEntriesApiResourceSwagger { public String createdByUserName; @Schema(example = "[2022, 07, 01]") public LocalDate createdDate; + @Schema(example = "qwerty1234") + public String externalAssetOwner; public CurrencyItem currency; public EnumOptionType glAccountType; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java index 021822d0e..68bf33aa2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java @@ -99,7 +99,8 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat .append(" creatingUser.username as createdByUserName, journalEntry.description as comments, ") .append(" journalEntry.submitted_on_date as submittedOnDate, journalEntry.reversed as reversed, ") .append(" journalEntry.currency_code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ") - .append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf "); + .append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ") + .append(" eao.external_id as externalAssetOwner "); if (associationParametersData.isRunningBalanceRequired()) { sb.append(" ,journalEntry.is_running_balance_calculated as runningBalanceComputed, ") .append(" journalEntry.office_running_balance as officeRunningBalance, ") @@ -117,7 +118,9 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat .append(" left join acc_gl_account as glAccount on glAccount.id = journalEntry.account_id") .append(" left join m_office as office on office.id = journalEntry.office_id") .append(" left join m_appuser as creatingUser on creatingUser.id = journalEntry.created_by ") - .append(" join m_currency curr on curr.code = journalEntry.currency_code "); + .append(" join m_currency curr on curr.code = journalEntry.currency_code ") + .append(" left join m_external_asset_owner_journal_entry_mapping eajem on eajem.journal_entry_id = journalEntry.id ") + .append(" left join m_external_asset_owner eao on eao.id = eajem.owner_id "); if (associationParametersData.isTransactionDetailsRequired()) { sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ") .append(" left join m_savings_account_transaction as st on journalEntry.savings_transaction_id = st.id ") @@ -150,7 +153,6 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat EnumOptionData entityType = null; if (entityTypeId != null) { entityType = AccountingEnumerations.portfolioProductType(entityTypeId); - } final Long entityId = JdbcSupport.getLong(rs, "entityId"); @@ -224,10 +226,12 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat transactionDetailData = new TransactionDetailData(transaction, paymentDetailData, noteData, transactionTypeEnumData); } + final String externalAssetOwner = rs.getString("externalAssetOwner"); + return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glCode, accountType, transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate, createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance, - runningBalanceComputed, transactionDetailData, currency); + runningBalanceComputed, transactionDetailData, currency, externalAssetOwner); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java index 9b669f2cb..d497f5fc3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryRunningBalanceUpdateServiceImpl.java @@ -278,7 +278,7 @@ public class JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntry final EnumOptionData entryType = AccountingEnumerations.journalEntryType(entryTypeId); return new JournalEntryData(id, officeId, null, null, glAccountId, null, accountType, null, entryType, amount, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null, null, null, null, null); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index 1aec81280..9aeb95364 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -27,6 +27,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -60,17 +61,25 @@ import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry; import org.apache.fineract.accounting.rule.domain.AccountingRule; import org.apache.fineract.accounting.rule.domain.AccountingRuleRepository; import org.apache.fineract.accounting.rule.exception.AccountingRuleNotFoundException; +import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; +import org.apache.fineract.infrastructure.configuration.service.ConfigurationReadPlatformService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.ErrorHandler; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.investor.domain.ExternalAssetOwner; +import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository; +import org.apache.fineract.investor.exception.ExternalAssetOwnerNotFoundException; +import org.apache.fineract.investor.service.AccountingService; import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; @@ -105,6 +114,9 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper; private final CashBasedAccountingProcessorForClientTransactions accountingProcessorForClientTransactions; + private final ConfigurationReadPlatformService configurationReadPlatformService; + private final AccountingService accountingService; + private final ExternalAssetOwnerRepository externalAssetOwnerRepository; @Transactional @Override @@ -131,6 +143,22 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final String transactionId = generateTransactionId(officeId); final String referenceNumber = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.REFERENCE_NUMBER.getValue()); + ExternalAssetOwner externalAssetOwner = null; + final ExternalId externalId = ExternalIdFactory + .produce(command.stringValueOfParameterNamed(JournalEntryJsonInputParams.EXTERNAL_ASSET_OWNER.getValue())); + if (!externalId.isEmpty()) { + if (!configurationReadPlatformService + .retrieveGlobalConfiguration(GlobalConfigurationConstants.ASSET_EXTERNALIZATION_OF_NON_ACTIVE_LOANS).isEnabled()) { + throw new JournalEntryRuntimeException("error.msg.glJournalEntry.asset.externalization.not.enabled", + "GL Journal Entry with Asset Externalization not enabled"); + } + final Optional optExternalAssetOwner = externalAssetOwnerRepository.findByExternalId(externalId); + if (!optExternalAssetOwner.isPresent()) { + throw new ExternalAssetOwnerNotFoundException(externalId); + } + externalAssetOwner = optExternalAssetOwner.get(); + } + if (accountRuleId != null) { final AccountingRule accountingRule = this.accountingRuleRepository.findById(accountRuleId) @@ -147,13 +175,13 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa } saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate, - journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber); + journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber, externalAssetOwner); } else { final GLAccount creditAccountHead = accountingRule.getAccountToCredit(); validateGLAccountForTransaction(creditAccountHead); validateDebitOrCreditArrayForExistingGLAccount(creditAccountHead, journalEntryCommand.getCredits()); saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate, - journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber); + journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber, externalAssetOwner); } if (accountingRule.getAccountToDebit() == null) { @@ -167,21 +195,21 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa } saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate, - journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber); + journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber, externalAssetOwner); } else { final GLAccount debitAccountHead = accountingRule.getAccountToDebit(); validateGLAccountForTransaction(debitAccountHead); validateDebitOrCreditArrayForExistingGLAccount(debitAccountHead, journalEntryCommand.getDebits()); saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate, - journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber); + journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber, externalAssetOwner); } } else { saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate, - journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber); + journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber, externalAssetOwner); saveAllDebitOrCreditEntries(journalEntryCommand, office, paymentDetail, currencyCode, transactionDate, - journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber); + journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber, externalAssetOwner); } @@ -598,7 +626,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa private void saveAllDebitOrCreditEntries(final JournalEntryCommand command, final Office office, final PaymentDetail paymentDetail, final String currencyCode, final LocalDate transactionDate, final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, final String transactionId, - final JournalEntryType type, final String referenceNumber) { + final JournalEntryType type, final String referenceNumber, final ExternalAssetOwner externalAssetOwner) { final boolean manualEntry = true; for (final SingleDebitOrCreditEntryCommand singleDebitOrCreditEntryCommand : singleDebitOrCreditEntryCommands) { final GLAccount glAccount = this.glAccountRepository.findById(singleDebitOrCreditEntryCommand.getGlAccountId()) @@ -618,6 +646,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa manualEntry, transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, referenceNumber, null, null, null, null); helper.persistJournalEntry(glJournalEntry); + + accountingService.createMappingToOwner(externalAssetOwner, glJournalEntry); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java index c22fbf394..397e486ce 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java @@ -35,11 +35,14 @@ import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlat import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformServiceJpaRepositoryImpl; import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; import org.apache.fineract.accounting.rule.domain.AccountingRuleRepository; +import org.apache.fineract.infrastructure.configuration.service.ConfigurationReadPlatformService; import org.apache.fineract.infrastructure.core.service.PaginationHelper; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository; +import org.apache.fineract.investor.service.AccountingService; import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.office.domain.OfficeRepository; import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; @@ -89,11 +92,14 @@ public class AccountingJournalEntryConfiguration { GLAccountReadPlatformService glAccountReadPlatformService, OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository, PlatformSecurityContext context, PaymentDetailWritePlatformService paymentDetailWritePlatformService, FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper, - CashBasedAccountingProcessorForClientTransactions accountingProcessorForClientTransactions) { + CashBasedAccountingProcessorForClientTransactions accountingProcessorForClientTransactions, + ConfigurationReadPlatformService configurationReadPlatformService, AccountingService accountingService, + ExternalAssetOwnerRepository externalAssetOwnerRepository) { return new JournalEntryWritePlatformServiceJpaRepositoryImpl(glClosureRepository, glAccountRepository, glJournalEntryRepository, officeRepositoryWrapper, accountingProcessorForLoanFactory, accountingProcessorForSavingsFactory, accountingProcessorForSharesFactory, helper, fromApiJsonDeserializer, accountingRuleRepository, glAccountReadPlatformService, organisationCurrencyRepository, context, paymentDetailWritePlatformService, - financialActivityAccountRepositoryWrapper, accountingProcessorForClientTransactions); + financialActivityAccountRepositoryWrapper, accountingProcessorForClientTransactions, configurationReadPlatformService, + accountingService, externalAssetOwnerRepository); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java index 47a80859b..fa529610f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java @@ -1301,5 +1301,4 @@ public class AccountingScenarioIntegrationTest { .withSubmittedDate("01 Jan 2016").withApplicationDate("01 Jan 2016").withRequestedShares("100").build(); return ShareAccountTransactionHelper.createShareAccount(shareAccountJSON, requestSpec, responseSpec); } - } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java index cf52c99ce..62744304c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java @@ -192,4 +192,10 @@ public class JournalEntryHelper { public static PostJournalEntriesResponse createJournalEntry(String command, JournalEntryCommand request) { return Calls.ok(FineractClientHelper.getFineractClient().journalEntries.createGLJournalEntry(command, request)); } + + public static GetJournalEntriesTransactionIdResponse retrieveJournalEntryByTransactionId(final String transactionId) { + return Calls.ok(FineractClientHelper.getFineractClient().journalEntries.retrieveAll1(// + null, null, null, null, null, null, null, transactionId, null, // + null, null, null, null, null, null, null, null, null, true)); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java index 327d2a695..15e9aca31 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java @@ -60,11 +60,20 @@ import org.apache.fineract.client.models.ExternalOwnerJournalEntryData; import org.apache.fineract.client.models.ExternalOwnerTransferJournalEntryData; import org.apache.fineract.client.models.ExternalTransferData; import org.apache.fineract.client.models.GetFinancialActivityAccountsResponse; +import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse; +import org.apache.fineract.client.models.JournalEntryCommand; +import org.apache.fineract.client.models.JournalEntryTransactionItem; import org.apache.fineract.client.models.PageExternalTransferData; import org.apache.fineract.client.models.PostFinancialActivityAccountsRequest; import org.apache.fineract.client.models.PostInitiateTransferResponse; +import org.apache.fineract.client.models.PostJournalEntriesResponse; +import org.apache.fineract.client.models.PostLoanProductsResponse; +import org.apache.fineract.client.models.PostLoansLoanIdRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; +import org.apache.fineract.client.models.PostLoansRequest; +import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.client.models.PutGlobalConfigurationsRequest; +import org.apache.fineract.client.models.SingleDebitOrCreditEntryCommand; import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; import org.apache.fineract.infrastructure.event.external.data.ExternalEventResponse; @@ -80,6 +89,7 @@ import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.AccountHelper; import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper; +import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper; import org.apache.fineract.integrationtests.common.charges.ChargesHelper; import org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper; import org.apache.fineract.integrationtests.common.externalevents.ExternalEventsExtension; @@ -1178,6 +1188,69 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT } } + @Test + public void addManualJournalEntriesWithAssetExternalization() { + runAt("10 April 2025", () -> { + + final Account glAccountDebit = accountHelper.createAssetAccount(); + final Account glAccountCredit = accountHelper.createLiabilityAccount(); + final String externalAssetOwner = Utils.uniqueRandomStringGenerator("ASSET_EXTERNAL_", 5); + + CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class, + () -> JournalEntryHelper.createJournalEntry("", new JournalEntryCommand().amount(BigDecimal.TEN).officeId(1L) + .currencyCode("USD").locale("en").dateFormat("uuuu-MM-dd").transactionDate(LocalDate.of(2024, 1, 1)) + .addCreditsItem(new SingleDebitOrCreditEntryCommand().glAccountId(glAccountDebit.getAccountID().longValue()) + .amount(BigDecimal.TEN)) + .addDebitsItem(new SingleDebitOrCreditEntryCommand().glAccountId(glAccountCredit.getAccountID().longValue()) + .amount(BigDecimal.TEN)) + .externalAssetOwner(externalAssetOwner))); + Assertions.assertTrue(callFailedRuntimeException.getMessage().contains("External asset owner with external id:")); + + final Integer clientId = ClientHelper.createClient(requestSpec, responseSpec); + final String operationDate = "10 April 2025"; + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct( + createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocationAndInterestRecalculation(12.0, 4)); + + final PostLoansRequest applicationRequest = applyLoanRequest(clientId.longValue(), loanProductResponse.getResourceId(), + operationDate, 1000.0, 4).transactionProcessingStrategyCode("advanced-payment-allocation-strategy")// + .interestRatePerPeriod(BigDecimal.valueOf(12.0)); + + final PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest); + final Long loanId = loanResponse.getLoanId(); + + loanTransactionHelper.approveLoan(loanId, new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en")); + + loanTransactionHelper.disburseLoan(loanId, new PostLoansLoanIdRequest().actualDisbursementDate(operationDate) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + + PostInitiateTransferResponse transferResponse = EXTERNAL_ASSET_OWNER_HELPER.initiateTransferByLoanId(loanId, "sale", + new ExternalAssetOwnerRequest().settlementDate("2025-04-20").dateFormat("yyyy-MM-dd").locale("en") + .transferExternalId(externalAssetOwner).transferExternalGroupId(null).ownerExternalId(externalAssetOwner) + .purchasePriceRatio("0.90")); + assertEquals(externalAssetOwner, transferResponse.getResourceExternalId()); + + final PostJournalEntriesResponse journalEntriesResponse = JournalEntryHelper.createJournalEntry("", + new JournalEntryCommand().amount(BigDecimal.TEN).officeId(1L).currencyCode("USD").locale("en").dateFormat("uuuu-MM-dd") + .transactionDate(LocalDate.of(2024, 1, 1)) + .addCreditsItem(new SingleDebitOrCreditEntryCommand().glAccountId(glAccountDebit.getAccountID().longValue()) + .amount(BigDecimal.TEN)) + .addDebitsItem(new SingleDebitOrCreditEntryCommand().glAccountId(glAccountCredit.getAccountID().longValue()) + .amount(BigDecimal.TEN)) + .externalAssetOwner(externalAssetOwner)); + + final GetJournalEntriesTransactionIdResponse journalEntriesTransactionIdResponse = JournalEntryHelper + .retrieveJournalEntryByTransactionId(journalEntriesResponse.getTransactionId()); + Assertions.assertNotNull(journalEntriesTransactionIdResponse); + assertEquals(2, journalEntriesTransactionIdResponse.getPageItems().size()); + JournalEntryTransactionItem journalEntryItem = journalEntriesTransactionIdResponse.getPageItems().get(0); + assertEquals(externalAssetOwner, journalEntryItem.getExternalAssetOwner()); + journalEntryItem = journalEntriesTransactionIdResponse.getPageItems().get(1); + assertEquals(externalAssetOwner, journalEntryItem.getExternalAssetOwner()); + }); + } + private void updateBusinessDateAndExecuteCOBJob(String date) { BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC, BUSINESS_DATE, LocalDate.parse(date)); SCHEDULER_JOB_HELPER.executeAndAwaitJob("Loan COB");