updade code based on the accounting deatils search

This commit is contained in:
Nitesh Yadav
2026-05-19 10:34:52 +05:30
parent 390bded7ef
commit f93a08c6b3
8 changed files with 37331 additions and 22466 deletions
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DayWiseSummaryData {
private LocalDate date;
private BigDecimal openingBalance;
private BigDecimal totalDebit;
private BigDecimal totalCredit;
private BigDecimal netMovement;
private BigDecimal closingBalance;
private int entryCount;
}
@@ -0,0 +1,72 @@
/**
* 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.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.infrastructure.core.service.Page;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GLAccountLedgerReportData {
/** GL Account details (name, code, type, description, usage) */
private GLAccountData glAccount;
/** Balance of the account before the fromDate (0 if no fromDate or no glAccountId) */
private BigDecimal openingBalance;
/** Balance of the account as of toDate = openingBalance + netMovement */
private BigDecimal closingBalance;
/** Sum of all DEBIT entries in the requested period */
private BigDecimal totalDebit;
/** Sum of all CREDIT entries in the requested period */
private BigDecimal totalCredit;
/**
* Net movement: for ASSET/EXPENSE (debit-normal) = totalDebit - totalCredit;
* for LIABILITY/INCOME/EQUITY (credit-normal) = totalCredit - totalDebit
*/
private BigDecimal netMovement;
/** true for ASSET and EXPENSE accounts (debit-normal balance convention) */
private boolean debitNormal;
private LocalDate fromDate;
private LocalDate toDate;
/** Day-wise breakdown of balances for the period */
private List<DayWiseSummaryData> dayWiseSummary;
/** Total number of journal entries matching the filter (for the KPI card) */
private long totalEntries;
/** Paginated journal entries for the period */
private Page<JournalEntryData> entries;
}
@@ -19,6 +19,7 @@
package org.apache.fineract.accounting.journalentry.service; package org.apache.fineract.accounting.journalentry.service;
import java.time.LocalDate; import java.time.LocalDate;
import org.apache.fineract.accounting.journalentry.data.GLAccountLedgerReportData;
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData; import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.apache.fineract.accounting.journalentry.data.JournalEntryData; import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData; import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData;
@@ -36,4 +37,11 @@ public interface JournalEntryReadPlatformService {
OfficeOpeningBalancesData retrieveOfficeOpeningBalances(Long officeId, String currencyCode); OfficeOpeningBalancesData retrieveOfficeOpeningBalances(Long officeId, String currencyCode);
Page<JournalEntryData> retrieveJournalEntriesByEntityId(String transactionId, Long entityId, Integer entityType); Page<JournalEntryData> retrieveJournalEntriesByEntityId(String transactionId, Long entityId, Integer entityType);
/**
* CBS GL Ledger Report: returns opening balance, day-wise summary, and paginated entries for a GL account.
* Opening balance is computed as the net balance of all entries before fromDate.
*/
GLAccountLedgerReportData retrieveGLAccountLedger(Long glAccountId, Long officeId, Boolean onlyManualEntries,
LocalDate fromDate, LocalDate toDate, String transactionId, SearchParameters searchParameters);
} }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -44,6 +44,7 @@ import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.journalentry.command.JournalEntryCommand; import org.apache.fineract.accounting.journalentry.command.JournalEntryCommand;
import org.apache.fineract.accounting.journalentry.data.GLAccountLedgerReportData;
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData; import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.apache.fineract.accounting.journalentry.data.JournalEntryData; import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData; import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData;
@@ -268,6 +269,57 @@ public class JournalEntriesApiResource {
return this.apiJsonSerializerService.serialize(settings, officeOpeningBalancesData); return this.apiJsonSerializerService.serialize(settings, officeOpeningBalancesData);
} }
/**
* CBS GL Account Ledger Report — returns opening balance, day-wise summary,
* period totals, and paginated entries for a given GL account and date range.
*
* GET /journalentries/ledger?glAccountId=5&fromDate=01 Jan 2025&toDate=31 Jan 2025&dateFormat=dd MMM yyyy&locale=en
*/
@GET
@Path("ledger")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "GL Account Ledger Report",
description = "Returns opening balance, day-wise running balance summary, period totals, and paginated journal entries for a GL account.")
public String retrieveGLAccountLedger(@Context final UriInfo uriInfo,
@QueryParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId,
@QueryParam("officeId") @Parameter(description = "officeId") final Long officeId,
@QueryParam("manualEntriesOnly") @Parameter(description = "manualEntriesOnly") final Boolean onlyManualEntries,
@QueryParam("fromDate") @Parameter(description = "fromDate") final DateParam fromDateParam,
@QueryParam("toDate") @Parameter(description = "toDate") final DateParam toDateParam,
@QueryParam("transactionId") @Parameter(description = "transactionId") final String transactionId,
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
@QueryParam("locale") @Parameter(description = "locale") final String locale,
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION);
final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat);
LocalDate fromDate = null;
if (fromDateParam != null) {
fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
}
LocalDate toDate = null;
if (toDateParam != null) {
toDate = toDateParam.getDate("toDate", dateFormat, locale);
}
sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder()
.limit(limit).officeId(officeId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final GLAccountLedgerReportData ledgerData = this.journalEntryReadPlatformService
.retrieveGLAccountLedger(glAccountId, officeId, onlyManualEntries, fromDate, toDate, transactionId, searchParameters);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.apiJsonSerializerService.serialize(settings, ledgerData);
}
private boolean is(final String commandParam, final String commandValue) { private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
} }
@@ -28,6 +28,7 @@ import java.time.OffsetDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -37,6 +38,8 @@ import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialA
import org.apache.fineract.accounting.glaccount.data.GLAccountData; import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType; import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService; import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
import org.apache.fineract.accounting.journalentry.data.DayWiseSummaryData;
import org.apache.fineract.accounting.journalentry.data.GLAccountLedgerReportData;
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData; import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.apache.fineract.accounting.journalentry.data.JournalEntryData; import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData; import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData;
@@ -213,16 +216,20 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
TransactionTypeEnumData transactionTypeEnumData = null; TransactionTypeEnumData transactionTypeEnumData = null;
if (PortfolioAccountType.fromInt(entityTypeId).isLoanAccount()) { if (entityTypeId != null && PortfolioAccountType.fromInt(entityTypeId).isLoanAccount()) {
final LoanTransactionEnumData loanTransactionType = LoanEnumerations final Integer loanTxnTypeId = JdbcSupport.getInteger(rs, "loanTransactionType");
.transactionType(JdbcSupport.getInteger(rs, "loanTransactionType")); if (loanTxnTypeId != null) {
transactionTypeEnumData = new TransactionTypeEnumData(loanTransactionType.getId(), loanTransactionType.getCode(), final LoanTransactionEnumData loanTransactionType = LoanEnumerations.transactionType(loanTxnTypeId);
loanTransactionType.getValue()); transactionTypeEnumData = new TransactionTypeEnumData(loanTransactionType.getId(), loanTransactionType.getCode(),
} else if (PortfolioAccountType.fromInt(entityTypeId).isSavingsAccount()) { loanTransactionType.getValue());
final SavingsAccountTransactionEnumData savingsTransactionType = SavingsEnumerations }
.transactionType(JdbcSupport.getInteger(rs, "savingsTransactionType")); } else if (entityTypeId != null && PortfolioAccountType.fromInt(entityTypeId).isSavingsAccount()) {
transactionTypeEnumData = new TransactionTypeEnumData(savingsTransactionType.getId(), savingsTransactionType.getCode(), final Integer savingsTxnTypeId = JdbcSupport.getInteger(rs, "savingsTransactionType");
savingsTransactionType.getValue()); if (savingsTxnTypeId != null) {
final SavingsAccountTransactionEnumData savingsTransactionType = SavingsEnumerations.transactionType(savingsTxnTypeId);
transactionTypeEnumData = new TransactionTypeEnumData(savingsTransactionType.getId(), savingsTransactionType.getCode(),
savingsTransactionType.getValue());
}
} }
transactionDetailData = new TransactionDetailData(transaction, paymentDetailData, noteData, transactionTypeEnumData); transactionDetailData = new TransactionDetailData(transaction, paymentDetailData, noteData, transactionTypeEnumData);
@@ -539,4 +546,212 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
throw new JournalEntriesNotFoundException(entityId, e); throw new JournalEntriesNotFoundException(entityId, e);
} }
} }
@Override
public GLAccountLedgerReportData retrieveGLAccountLedger(final Long glAccountId, final Long officeId,
final Boolean onlyManualEntries, final LocalDate fromDate, final LocalDate toDate,
final String transactionId, final SearchParameters searchParameters) {
// ── 1. Fetch GL Account details ──────────────────────────────────────
GLAccountData glAccountData = null;
boolean isDebitNormal = true; // ASSET / EXPENSE = debit-normal
if (glAccountId != null && glAccountId > 0) {
glAccountData = this.glAccountReadPlatformService.retrieveGLAccountById(glAccountId,
new JournalEntryAssociationParametersData());
if (glAccountData != null && glAccountData.getType() != null) {
final int typeId = glAccountData.getType().getId().intValue();
// 1=ASSET, 2=LIABILITY, 3=EQUITY, 4=INCOME, 5=EXPENSE
isDebitNormal = (typeId == 1 || typeId == 5);
}
}
// ── 2. Compute opening balance (all entries before fromDate) ─────────
BigDecimal openingBalance = BigDecimal.ZERO;
if (glAccountId != null && glAccountId > 0 && fromDate != null) {
final List<Object> obParams = new ArrayList<>();
final StringBuilder obSql = new StringBuilder(
"SELECT COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS ob_debit, "
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS ob_credit "
+ "FROM acc_gl_journal_entry je "
+ "WHERE je.account_id = ? AND je.entry_date < ?");
obParams.add(glAccountId);
obParams.add(fromDate);
if (officeId != null && officeId > 0) {
obSql.append(" AND je.office_id = ?");
obParams.add(officeId);
}
final BigDecimal[] ob = this.jdbcTemplate.queryForObject(obSql.toString(),
(rs, rowNum) -> new BigDecimal[]{
Objects.requireNonNullElse(rs.getBigDecimal("ob_debit"), BigDecimal.ZERO),
Objects.requireNonNullElse(rs.getBigDecimal("ob_credit"), BigDecimal.ZERO)
}, obParams.toArray());
if (ob != null) {
openingBalance = isDebitNormal ? ob[0].subtract(ob[1]) : ob[1].subtract(ob[0]);
}
}
// ── 3. Day-wise summary for the period ───────────────────────────────
final List<DayWiseSummaryData> dayWiseSummary = new ArrayList<>();
{
final List<Object> dwParams = new ArrayList<>();
final StringBuilder dwSql = new StringBuilder(
"SELECT je.entry_date AS dw_date, "
+ "COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS dw_debit, "
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS dw_credit, "
+ "COUNT(*) AS dw_count "
+ "FROM acc_gl_journal_entry je "
+ "WHERE 1=1");
if (glAccountId != null && glAccountId > 0) {
dwSql.append(" AND je.account_id = ?");
dwParams.add(glAccountId);
}
if (officeId != null && officeId > 0) {
dwSql.append(" AND je.office_id = ?");
dwParams.add(officeId);
}
if (fromDate != null) {
dwSql.append(" AND je.entry_date >= ?");
dwParams.add(fromDate);
}
if (toDate != null) {
dwSql.append(" AND je.entry_date <= ?");
dwParams.add(toDate);
}
if (onlyManualEntries != null && onlyManualEntries) {
dwSql.append(" AND je.manual_entry = true");
}
dwSql.append(" GROUP BY je.entry_date ORDER BY je.entry_date ASC");
final boolean debitNormalFinal = isDebitNormal;
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(dwSql.toString(), dwParams.toArray());
BigDecimal running = openingBalance;
for (final Map<String, Object> row : rows) {
// Handle both java.sql.Date (older drivers) and java.time.LocalDate (newer drivers)
final Object rawDate = row.get("dw_date");
LocalDate date;
if (rawDate instanceof LocalDate) {
date = (LocalDate) rawDate;
} else if (rawDate instanceof java.sql.Date) {
date = ((java.sql.Date) rawDate).toLocalDate();
} else {
date = LocalDate.parse(String.valueOf(rawDate).substring(0, 10));
}
final BigDecimal dr = Objects.requireNonNullElse((BigDecimal) row.get("dw_debit"), BigDecimal.ZERO);
final BigDecimal cr = Objects.requireNonNullElse((BigDecimal) row.get("dw_credit"), BigDecimal.ZERO);
final int count = ((Number) row.get("dw_count")).intValue();
final BigDecimal net = debitNormalFinal ? dr.subtract(cr) : cr.subtract(dr);
final BigDecimal closing = running.add(net);
dayWiseSummary.add(DayWiseSummaryData.builder()
.date(date)
.openingBalance(running)
.totalDebit(dr)
.totalCredit(cr)
.netMovement(net)
.closingBalance(closing)
.entryCount(count)
.build());
running = closing;
}
}
// ── 4. Paginated journal entries — only fetch if limit > 0 ───────────
final JournalEntryAssociationParametersData assocParams = new JournalEntryAssociationParametersData(true, true);
final int reqLimit = searchParameters != null && searchParameters.getLimit() != null
? searchParameters.getLimit() : 0;
final Page<JournalEntryData> entries;
if (reqLimit > 0) {
entries = retrieveAll(searchParameters, glAccountId, onlyManualEntries,
fromDate, toDate, null, null, transactionId, null, assocParams);
} else {
// Summary-only request — return empty page, no SQL needed
entries = new Page<>(new ArrayList<>(), 0);
}
// ── 5. Compute period totals ─────────────────────────────────────────
BigDecimal totalDebit = BigDecimal.ZERO;
BigDecimal totalCredit = BigDecimal.ZERO;
{
final List<Object> totParams = new ArrayList<>();
final StringBuilder totSql = new StringBuilder(
"SELECT COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS totalDebit, "
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS totalCredit "
+ "FROM acc_gl_journal_entry je WHERE 1=1");
if (glAccountId != null && glAccountId > 0) {
totSql.append(" AND je.account_id = ?");
totParams.add(glAccountId);
}
if (officeId != null && officeId > 0) {
totSql.append(" AND je.office_id = ?");
totParams.add(officeId);
}
if (fromDate != null) {
totSql.append(" AND je.entry_date >= ?");
totParams.add(fromDate);
}
if (toDate != null) {
totSql.append(" AND je.entry_date <= ?");
totParams.add(toDate);
}
if (onlyManualEntries != null && onlyManualEntries) {
totSql.append(" AND je.manual_entry = true");
}
final BigDecimal[] totResult = this.jdbcTemplate.queryForObject(totSql.toString(),
(rs, rowNum) -> new BigDecimal[]{
Objects.requireNonNullElse(rs.getBigDecimal("totalDebit"), BigDecimal.ZERO),
Objects.requireNonNullElse(rs.getBigDecimal("totalCredit"), BigDecimal.ZERO)
}, totParams.toArray());
if (totResult != null) {
totalDebit = totResult[0];
totalCredit = totResult[1];
}
}
final BigDecimal netMovement = isDebitNormal ? totalDebit.subtract(totalCredit) : totalCredit.subtract(totalDebit);
final BigDecimal closingBalance = openingBalance.add(netMovement);
// ── 6. Total entries count ────────────────────────────────────────────
long totalEntries = 0L;
{
final List<Object> cntParams = new ArrayList<>();
final StringBuilder cntSql = new StringBuilder(
"SELECT COUNT(*) FROM acc_gl_journal_entry je WHERE 1=1");
if (glAccountId != null && glAccountId > 0) {
cntSql.append(" AND je.account_id = ?");
cntParams.add(glAccountId);
}
if (officeId != null && officeId > 0) {
cntSql.append(" AND je.office_id = ?");
cntParams.add(officeId);
}
if (fromDate != null) {
cntSql.append(" AND je.entry_date >= ?");
cntParams.add(fromDate);
}
if (toDate != null) {
cntSql.append(" AND je.entry_date <= ?");
cntParams.add(toDate);
}
if (onlyManualEntries != null && onlyManualEntries) {
cntSql.append(" AND je.manual_entry = true");
}
final Long cnt = this.jdbcTemplate.queryForObject(cntSql.toString(), Long.class, cntParams.toArray());
if (cnt != null) { totalEntries = cnt; }
}
return GLAccountLedgerReportData.builder()
.glAccount(glAccountData)
.openingBalance(openingBalance)
.closingBalance(closingBalance)
.totalDebit(totalDebit)
.totalCredit(totalCredit)
.netMovement(netMovement)
.debitNormal(isDebitNormal)
.fromDate(fromDate)
.toDate(toDate)
.dayWiseSummary(dayWiseSummary)
.totalEntries(totalEntries)
.entries(entries)
.build();
}
} }
@@ -595,26 +595,29 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
return; return;
} }
final String transactionRef = "CA" + account.getId(); // Deposit uses CA{accountId} as transaction ref (one per activation)
final String depositRef = "CA" + account.getId();
// Deposits: DR SAVINGS_REFERENCE (cash in), CR SAVINGS_CONTROL (liability) // Deposits: DR SAVINGS_REFERENCE (cash in), CR SAVINGS_CONTROL (liability)
if (depositTxns != null) { if (depositTxns != null) {
for (final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn : depositTxns) { for (final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn : depositTxns) {
if (txn.getId() == null || txn.getAmount() == null) continue; if (txn.getId() == null || txn.getAmount() == null) continue;
postEntry(account, cashAccount, JournalEntryType.DEBIT, txn.getAmount(), txn.getTransactionDate(), transactionRef); postEntry(account, cashAccount, JournalEntryType.DEBIT, txn.getAmount(), txn.getTransactionDate(), depositRef);
postEntry(account, controlAccount, JournalEntryType.CREDIT, txn.getAmount(), txn.getTransactionDate(), transactionRef); postEntry(account, controlAccount, JournalEntryType.CREDIT, txn.getAmount(), txn.getTransactionDate(), depositRef);
log.info("[GL] Deposit GL posted for account id={}, amount={}", account.getId(), txn.getAmount()); log.info("[GL] Deposit GL posted for account id={}, amount={}", account.getId(), txn.getAmount());
} }
} }
// Charges: DR SAVINGS_CONTROL (liability reduced), CR INCOME_FROM_FEES // Charges: each charge gets its own transaction ref = "CAC" + txn.getId()
// so each charge appears as a separate transaction in journal entries
if (chargeEntries != null && feeIncomeAccount != null) { if (chargeEntries != null && feeIncomeAccount != null) {
for (final ChargeGlEntry entry : chargeEntries) { for (final ChargeGlEntry entry : chargeEntries) {
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn = entry.txn(); final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn = entry.txn();
if (txn.getId() == null || txn.getAmount() == null) continue; if (txn.getId() == null || txn.getAmount() == null) continue;
postEntry(account, controlAccount, JournalEntryType.DEBIT, txn.getAmount(), txn.getTransactionDate(), transactionRef); final String chargeRef = "CAC" + txn.getId();
postEntry(account, feeIncomeAccount, JournalEntryType.CREDIT, txn.getAmount(), txn.getTransactionDate(), transactionRef); postEntry(account, controlAccount, JournalEntryType.DEBIT, txn.getAmount(), txn.getTransactionDate(), chargeRef);
log.info("[GL] Charge GL posted for account id={}, charge={}, amount={}", account.getId(), entry.charge().getCharge().getName(), txn.getAmount()); postEntry(account, feeIncomeAccount, JournalEntryType.CREDIT, txn.getAmount(), txn.getTransactionDate(), chargeRef);
log.info("[GL] Charge GL posted for account id={}, charge={}, amount={}, ref={}", account.getId(), entry.charge().getCharge().getName(), txn.getAmount(), chargeRef);
} }
} }
} catch (Exception e) { } catch (Exception e) {