FINERACT-2198: Extend Manual Journal entry to support Asset Externalization

This commit is contained in:
Jose Alberto Hernandez
2025-07-07 13:59:33 +02:00
committed by Adam Saghy
parent 4feee70a7a
commit 0c1ca2baba
16 changed files with 190 additions and 21 deletions
@@ -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;
@@ -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);
}
}
@@ -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);
}
}
@@ -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<ExternalAssetOwner> 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);
}
}
@@ -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);
}
}