fix(deposits): Deposit Account Link wrongly demanded a co-op share account
SYMPTOM Creating an FD/RD account with "Deposit Account Link" enabled and a Savings/Current account correctly picked in the UI still failed with: "A share account must be chosen when Share Linking is enabled." even though no share account field exists anywhere on the FD/RD form. ROOT CAUSE SavingsAccountAssembler.applyCbsFields() has one boolean flag, shareLinkingEnabled, serving two unrelated features that got collided together (traceable to commitdc0539d53, which repurposed the flag for co-op Share Account linking on plain Savings products): - On a plain Savings product: "Share Linking" = link a co-op m_share_account (shareAccountId). - On an FD/RD product: the SAME flag is literally labeled "Deposit Account Link" on the product screen (fixed-deposit-product-details-step.component.html) and is meant to require the pre-existing, native linkedAccountId field (already wired end-to-end: SavingsAccountAssembler sets it a few lines below this check, the FD "Maturity Instructions: Transfer to Savings" flow already depends on it, DepositAccountReadPlatformServiceImpl already reads it back for display). Beforedc0539d53, 7 real historical FD accounts (ids 51-57 in fineract_default) prove linkedAccountId was the correct, working mechanism for this. The block added by that commit made shareAccountId mandatory unconditionally, for every product, breaking FD/RD. FIX SavingsAccountAssembler.applyCbsFields(): when shareLinkingEnabled is true, branch on account.depositAccountType(): - FIXED_DEPOSIT / RECURRING_DEPOSIT: require linkedAccountId (the field the FD/RD form already sends) instead of shareAccountId. - everything else (plain Savings): unchanged, still requires and validates shareAccountId against m_share_account. SCOPE Single method, single file. No new column, no new flag, no frontend change - the frontend's Deposit Account Link picker was already correct; only this one validation block was reading the wrong field for FD/RD. IF THIS BREAKS SOMETHING LATER - "A share account must be chosen..." reappearing for FD/RD -> check account.depositAccountType() is resolving to FIXED_DEPOSIT/ RECURRING_DEPOSIT at this point (it's a simple override per account subclass, see FixedDepositAccount#depositAccountType()). - A plain Savings product's genuine co-op Share Linking breaking -> that path (the `else` branch here) is untouched; if it breaks, the regression is elsewhere. - A much larger version of this fix (a whole new depositAccountLinkRequired flag decoupled from shareLinkingEnabled, ~18 files) was attempted earlier in the same session and fully reverted at the user's request after repeated Gradle build-cache issues made it impossible to verify was even running; this is the intentionally minimal replacement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+25
-12
@@ -414,20 +414,33 @@ public class SavingsAccountAssembler {
|
||||
// dependency on the shareaccounts domain class) rather than trusted blindly, the same way an
|
||||
// account number typed for a linked savings/loan account elsewhere in this form is verified
|
||||
// before being accepted.
|
||||
//
|
||||
// For a term deposit (FD/RD), the product screen labels this same flag "Deposit Account Link" --
|
||||
// it links an existing Savings/Current account (linkedAccountId, set below), not a co-op share
|
||||
// account, so it's validated against that field instead of shareAccountId.
|
||||
if (Boolean.TRUE.equals(account.getShareLinkingEnabled())) {
|
||||
final Long shareAccountId = this.fromApiJsonHelper.extractLongNamed(shareAccountIdParamName, element);
|
||||
if (shareAccountId == null) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.savingsaccount.share.account.required",
|
||||
"A share account must be chosen when Share Linking is enabled.");
|
||||
final boolean isTermDeposit = account.depositAccountType().isFixedDeposit() || account.depositAccountType().isRecurringDeposit();
|
||||
if (isTermDeposit) {
|
||||
final Long linkedAccountIdForCheck = this.fromApiJsonHelper.extractLongNamed(linkedAccountIdParamName, element);
|
||||
if (linkedAccountIdForCheck == null) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.savingsaccount.linked.account.required",
|
||||
"A Savings/Current account must be linked when Deposit Account Link is enabled.");
|
||||
}
|
||||
} else {
|
||||
final Long shareAccountId = this.fromApiJsonHelper.extractLongNamed(shareAccountIdParamName, element);
|
||||
if (shareAccountId == null) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.savingsaccount.share.account.required",
|
||||
"A share account must be chosen when Share Linking is enabled.");
|
||||
}
|
||||
final Integer matches = this.jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_share_account WHERE id = ? AND client_id = ? AND status_enum = 300", Integer.class,
|
||||
shareAccountId, account.clientId());
|
||||
if (matches == null || matches == 0) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.savingsaccount.share.account.not.active.for.client",
|
||||
"Share account " + shareAccountId + " is not an active share account for this client.");
|
||||
}
|
||||
account.setShareAccountId(shareAccountId);
|
||||
}
|
||||
final Integer matches = this.jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_share_account WHERE id = ? AND client_id = ? AND status_enum = 300", Integer.class,
|
||||
shareAccountId, account.clientId());
|
||||
if (matches == null || matches == 0) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.savingsaccount.share.account.not.active.for.client",
|
||||
"Share account " + shareAccountId + " is not an active share account for this client.");
|
||||
}
|
||||
account.setShareAccountId(shareAccountId);
|
||||
}
|
||||
|
||||
// Senior citizen rate — runs after validateDomainRules so it is the final persisted value
|
||||
|
||||
Reference in New Issue
Block a user