From 375c2c95b62f3757d81a18f394a7aa2f36139fc6 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Wed, 28 Oct 2026 22:04:48 +0530 Subject: [PATCH] fix(dds): approval of a calculation run shows the entries its posting will book - A calculation run books nothing on the ledger, yet its approval preview was built as a posting and showed "Agent Commission Expense not set on the DDS product". The preview now projects the entries the matching Posting run will make, marked projected - A commission calculation's split over product Commission Expense GLs is taken from the agent's collections no run has counted yet, up to the run's end date Co-Authored-By: Claude Sonnet 5.5 --- .../portfolio/dds/accounting/DdsLedger.java | 22 +++++++++++++++++++ .../service/DdsApprovalReadService.java | 17 ++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/dds/accounting/DdsLedger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/dds/accounting/DdsLedger.java index 5bbdd887f..6909bab99 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/dds/accounting/DdsLedger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/dds/accounting/DdsLedger.java @@ -508,6 +508,28 @@ public class DdsLedger { if (basis.isEmpty()) { basis = this.jdbcTemplate.queryForList(String.format(COMMISSION_BASIS_SQL, ""), agentId, payingRunId); // NOSONAR } + return shareOut(basis, amount); + } + + /** The collections a commission run would count if it were posted now: not counted by any run yet, up to a date. */ + private static final String COMMISSION_PENDING_SQL = "select upper(trim(p.sub_type)) as \"product\", t.commission_expense_gl_id as \"glId\", " + + "sum(pt.amount) as \"volume\" from m_dds_party_transaction pt join m_dds_party p on p.id = pt.party_id " + + "left join m_dds_account_type t on t.account_type_code = upper(trim(p.sub_type)) " + + "where pt.agent_id = ? and pt.transaction_type = 'PARTY_TRAN' and pt.direction = 'CREDIT' " + + "and pt.commission_batch_run_id is null and pt.transaction_date <= ? group by upper(trim(p.sub_type)), " + + "t.commission_expense_gl_id order by upper(trim(p.sub_type))"; + + /** + * Where an agent's commission will be booked once it is posted - for a calculation run, which books + * nothing itself but whose approval should say which commission accounts the Posting run will debit. + * The split is over the collections the calculation counts (none counted yet, up to its end date). + */ + public Map projectedCommissionSplit(final Long agentId, final BigDecimal amount, final LocalDate through) { + return shareOut(this.jdbcTemplate.queryForList(COMMISSION_PENDING_SQL, agentId, java.sql.Date.valueOf(through)), amount); // NOSONAR + } + + /** Shares an amount over the commission accounts of the products collected for, by volume; the last takes the rounding. */ + private Map shareOut(final List> basis, final BigDecimal amount) { if (basis.isEmpty()) { throw new DdsLedgerNotConfiguredException("a collection by this agent to tell which DDS product's Agent Commission Expense " + "account the commission belongs to"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/dds/approval/service/DdsApprovalReadService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/dds/approval/service/DdsApprovalReadService.java index ab62fa2dc..06e6e4596 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/dds/approval/service/DdsApprovalReadService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/dds/approval/service/DdsApprovalReadService.java @@ -582,13 +582,22 @@ public class DdsApprovalReadService { // customer's product's interest account, commission shared over the commission accounts // of the products each agent collected for, as the posting itself will book it. final String jobType = first("select job_type from m_dds_batch_run where id = ?", resourceId); + // A calculation books nothing itself; its lines are what the matching Posting run will + // book, shown so the checker sees which accounts and ledger accounts the figures belong to. + final boolean projected = jobType != null && jobType.endsWith("_CALCULATION"); if (jobType != null && jobType.startsWith("COMMISSION")) { final Map byAccount = new LinkedHashMap<>(); final Map names = new LinkedHashMap<>(); for (final Map runRow : this.jdbcTemplate.queryForList( // NOSONAR - "select agent_id, amount from m_dds_batch_run_row where batch_run_id = ? and amount > 0 order by id", resourceId)) { + "select agent_id, amount, to_date from m_dds_batch_run_row where batch_run_id = ? and amount > 0 order by id", + resourceId)) { try { - this.ddsLedger.commissionSplit(((Number) runRow.get("agent_id")).longValue(), (BigDecimal) runRow.get("amount"), resourceId) + final long agentOfRow = ((Number) runRow.get("agent_id")).longValue(); + final BigDecimal amountOfRow = (BigDecimal) runRow.get("amount"); + (projected && runRow.get("to_date") != null + ? this.ddsLedger.projectedCommissionSplit(agentOfRow, amountOfRow, + ((java.sql.Date) runRow.get("to_date")).toLocalDate()) + : this.ddsLedger.commissionSplit(agentOfRow, amountOfRow, resourceId)) .forEach((account, share) -> { byAccount.merge(account.getGlCode(), share, BigDecimal::add); names.put(account.getGlCode(), account.getName()); @@ -622,6 +631,10 @@ public class DdsApprovalReadService { lines.add(line("CR", (String) credited.get("accountNo"), (String) credited.get("accountName"), (String) credited.get("type"), (BigDecimal) credited.get("amount"))); } + if (projected) { + // Marked, so the review says these entries are made by the Posting run, not on approval. + lines.forEach(each -> each.put("projected", true)); + } } default -> { // A change to a party, agent or scheme moves no money and has no sides.