fix(shares): drop stale client-mismatch check, fix stale client_id on settle
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s

- Remove the client-mismatch validation in the Additional Shares tranche
  submit path (ShareAccountDataSerializer) for parity with the same decision
  already made on the first-application Apply Collection flow -- a voucher a
  counter was shown and allowed to pick should not then be rejected for the
  same reason at the very last step
- Rewrite m_share_advance_collection.client_id to the settling account's own
  client at the moment a voucher is actually applied (both
  ShareAdvanceCollectionApiResource.apply() and
  AdditionalShareTrancheFundingService.settle()) -- a voucher can now be found
  and applied from any member's screen, so whoever last searched it is not
  necessarily who its money became share capital for, and the stale value was
  misnaming the receipt in its own detail popup after settlement

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 13:56:49 +05:30
co-authored by Claude Sonnet 5
parent e4fad8b648
commit 6046134b51
3 changed files with 24 additions and 13 deletions
@@ -712,17 +712,24 @@ public class ShareAdvanceCollectionApiResource {
// Every receipt in this settlement is marked applied, not just the one in the path — they
// were all consumed by it, and an unmarked one would still show as money awaiting use. The fee
// receipt is marked applied too, even though it posted no GL entry of its own.
//
// client_id is rewritten to this account's own client here too -- a voucher can now be found
// and applied from any member's screen, so whoever last searched it (the value it carried
// going in) is not necessarily who its money actually became share capital for. Leaving the
// old value in place would make the receipt's own record misname whose it is the moment it
// settles, which is exactly the figure a receipt-detail popup reads back afterwards.
final Object settledClientId = acct.get("client_id");
for (final Long id : collectionIds) {
jdbcTemplate.update(
"UPDATE m_share_advance_collection SET status = 'APPLIED', applied_share_account_id = ?, "
+ " applied_on_date = ?, applied_gl_transaction_id = ? WHERE id = ?",
shareAccountId, java.sql.Date.valueOf(entryDate), txnId, id);
+ " applied_on_date = ?, applied_gl_transaction_id = ?, client_id = ? WHERE id = ?",
shareAccountId, java.sql.Date.valueOf(entryDate), txnId, settledClientId, id);
}
if (chargeCollectionId != null) {
jdbcTemplate.update(
"UPDATE m_share_advance_collection SET status = 'APPLIED', applied_share_account_id = ?, "
+ " applied_on_date = ?, applied_gl_transaction_id = ? WHERE id = ?",
shareAccountId, java.sql.Date.valueOf(entryDate), txnId, chargeCollectionId);
+ " applied_on_date = ?, applied_gl_transaction_id = ?, client_id = ? WHERE id = ?",
shareAccountId, java.sql.Date.valueOf(entryDate), txnId, settledClientId, chargeCollectionId);
}
// Reveals the purchase transaction in the passbook and transactions views.
@@ -894,13 +894,11 @@ public class ShareAccountDataSerializer {
// already used" does not say which one to go and look at.
final Object label = collection.get("voucher_no") != null ? collection.get("voucher_no") : collection.get("receipt_no");
final Number collectionClientId = (Number) collection.get("client_id");
if (collectionClientId == null || collectionClientId.longValue() != account.getClientId().longValue()) {
baseDataValidator.reset().parameter(ShareAccountApiConstants.advancecollectionid_paramname).value(advanceCollectionId)
.failWithCodeAndMessage("share.advance.collection.client.mismatch",
"Voucher " + label + " belongs to a different member.");
}
// No client-match check here, matching the same product decision already applied to the
// first-application Apply Collection flow: a voucher is reachable and attachable from any
// account's screen (see ShareAdvanceCollectionApiResource), so rejecting it again here,
// at the very last step, would only fail a submission the counter was told was valid.
//
// Only money still sitting in Sundry can fund a tranche. APPLIED has already become
// share capital somewhere, and REFUNDED went back to the member.
final String status = (String) collection.get("status");
@@ -146,12 +146,18 @@ public class AdditionalShareTrancheFundingService {
}
// Every receipt behind the tranche, not just the lead one: a member's later instalments have
// become share capital too, and one left UNAPPLIED could be spent a second time.
//
// client_id is rewritten to this account's own client here too -- a voucher can now be found
// and applied from any member's screen, so whoever last searched it (the value it carried
// going in) is not necessarily who its money actually became share capital for. Leaving the
// old value in place would make the receipt's own record misname whose it is the moment it
// settles, which is exactly the figure a receipt-detail popup reads back afterwards.
for (final Long collectionId : transaction.getAllAdvanceCollectionIds()) {
this.jdbcTemplate.update("""
UPDATE m_share_advance_collection
SET status = 'APPLIED', applied_share_account_id = ?, applied_on_date = ?
SET status = 'APPLIED', applied_share_account_id = ?, applied_on_date = ?, client_id = ?
WHERE id = ? AND status = 'UNAPPLIED'
""", account.getId(), java.sql.Date.valueOf(DateUtils.getBusinessLocalDate()), collectionId);
""", account.getId(), java.sql.Date.valueOf(DateUtils.getBusinessLocalDate()), account.getClientId(), collectionId);
}
}