feat(dds): premature closure posts owed interest, account-based agent penalty, TDS and income GLs
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Waiting to run
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Waiting to run
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Waiting to run
Fineract Documentation build / build (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Waiting to run
Fineract Liquibase Only mode - PostgreSQL / test (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Waiting to run

- Premature closure credits the interest still owed through the day before
  closing itself instead of refusing; GET /dds/parties/{id}/interestdue
  previews it with the agent penalty, and pending approvals show the payout
  including it.
- Interest a customer gives back on closing early is booked to the product's
  new Premature Closure Income GL.
- Agent penalty is the scheme's rate on that account's own collection, capped
  at the commission it earned them, instead of a share of everything the agent
  is owed. Charges are held once in m_dds_agent_premature_charge rather than
  as a standing deduction every commission run took again; a commission
  calculation recovers them and its posting settles them - the penalty as
  reversed commission expense, funded interest moved back from interest
  expense.
- Commission posting books the TDS withheld (194H) to the product's new TDS
  Payable GL.
- Migration 0629.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 12:43:07 +05:30
co-authored by Claude Opus 5.5
parent 6508dbed67
commit 37503b541c
13 changed files with 444 additions and 41 deletions
@@ -65,6 +65,12 @@ public final class DdsAccountTypeData implements Serializable {
private final Long commissionExpenseGlId;
private final String commissionExpenseGlCode;
private final String commissionExpenseGlName;
private final Long prematureIncomeGlId;
private final String prematureIncomeGlCode;
private final String prematureIncomeGlName;
private final Long tdsPayableGlId;
private final String tdsPayableGlCode;
private final String tdsPayableGlName;
private final LocalDateTime createdDate;
private final LocalDateTime lastModifiedDate;
}
@@ -133,6 +133,17 @@ public class DdsAccountType extends AbstractAuditableCustom {
@Column(name = "commission_expense_gl_id")
private Long commissionExpenseGlId;
/**
* What an early closure earns the bank: the interest a customer gives back, and the penalty taken off
* the agent's commission.
*/
@Column(name = "premature_income_gl_id")
private Long prematureIncomeGlId;
/** Where TDS withheld from the agents' commission on this product (Section 194H) is owed until paid over. */
@Column(name = "tds_payable_gl_id")
private Long tdsPayableGlId;
protected DdsAccountType() {
//
}
@@ -152,6 +163,8 @@ public class DdsAccountType extends AbstractAuditableCustom {
this.depositLiabilityGlId = command.longValueOfParameterNamed("depositLiabilityGlId");
this.interestExpenseGlId = command.longValueOfParameterNamed("interestExpenseGlId");
this.commissionExpenseGlId = command.longValueOfParameterNamed("commissionExpenseGlId");
this.prematureIncomeGlId = command.longValueOfParameterNamed("prematureIncomeGlId");
this.tdsPayableGlId = command.longValueOfParameterNamed("tdsPayableGlId");
// A newly created account type is available for use straight away.
this.active = !command.parameterExists("active") || command.booleanPrimitiveValueOfParameterNamed("active");
}
@@ -275,6 +288,15 @@ public class DdsAccountType extends AbstractAuditableCustom {
this.commissionExpenseGlId = command.longValueOfParameterNamed("commissionExpenseGlId");
actualChanges.put("commissionExpenseGlId", this.commissionExpenseGlId);
}
if (command.parameterExists("prematureIncomeGlId")
&& command.isChangeInLongParameterNamed("prematureIncomeGlId", this.prematureIncomeGlId)) {
this.prematureIncomeGlId = command.longValueOfParameterNamed("prematureIncomeGlId");
actualChanges.put("prematureIncomeGlId", this.prematureIncomeGlId);
}
if (command.parameterExists("tdsPayableGlId") && command.isChangeInLongParameterNamed("tdsPayableGlId", this.tdsPayableGlId)) {
this.tdsPayableGlId = command.longValueOfParameterNamed("tdsPayableGlId");
actualChanges.put("tdsPayableGlId", this.tdsPayableGlId);
}
if (command.isChangeInBooleanParameterNamed("active", this.active)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("active");