DSC-2FA login fix + Individual-only link pickers + Title Case names
- DSC-as-2FA login: UI's claimAndValidate() now calls a new
AuthenticationService.applyTwoFactorToken(token, liveSeconds) instead of
validateOTP(). The /v1/login/dsc/{rn}/claim response is already a minted
TFAccessToken — POSTing it to /twofactor/validate fails because that path
expects an in-memory OTPRequest. applyTwoFactorToken runs only the
post-success bookkeeping (interceptor.setTwoFactorAccessToken, setCredentials,
Authentication Success alert that drives router.navigate(['/']), and
sessionStorage persist of {token, validFrom, validTo}). Fixes the
OTPTokenInvalidException users hit after a successful DSC verify+claim.
- Individual-only filter for link-client pickers: ClientTextSearch gains a
legalFormType field; SearchingClientRepository accepts an Integer legalForm
value and adds cb.equal(r.get("legalForm"), ...) to the spec.
ClientSearchService.parseLegalForm tolerates INDIVIDUAL/PERSON/1 and
NON_INDIVIDUAL/ENTITY/2; unknown = no filter so a typo doesn't blank results.
UI: clients.service.ts searchByText gains a 7th optional arg; six link
pickers (guardian, nominee, family-member across create flow and post-create
tabs) now pass 'INDIVIDUAL'. General client list and signatory picker are
intentionally unchanged.
- Title-case client names on persistence: ClientWritePlatformServiceJpa
RepositoryImpl now runs first/middle/last/full name fields through
toTitleCase() on both create and update paths. "aditya kumar vyas",
"ADITYA KUMAR VYAS", "Aditya Kumar Vyas" all persist as "Aditya Kumar Vyas".
Whitespace-split title case; preserves hyphens/apostrophes inside words
without re-capitalising after them so clerks verifying against ID documents
see predictable output.
- Migration 0296: unique constraint to prevent duplicate nominee per account
type (the underlying repository + service changes also persisted in this
commit complete the enforcement path).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
e1d7db9b7c
commit
6558ea6998
+17
@@ -43,4 +43,21 @@ public interface ClientNomineeRepository extends JpaRepository<ClientNominee, Lo
|
||||
|
||||
@Query("SELECT COUNT(n) FROM ClientNominee n WHERE n.accountNo = :accountNo AND n.accountType = :accountType AND n.isDeleted = false")
|
||||
long countByAccountNoAndAccountType(@Param("accountNo") String accountNo, @Param("accountType") String accountType);
|
||||
|
||||
/**
|
||||
* Counts non-deleted nominee rows that link {@code nomineeClientId} as a nominee under
|
||||
* {@code clientId} for the given {@code accountType}. Backs the rule that the same person
|
||||
* may not be linked as a nominee more than once per (account owner, account type).
|
||||
* {@code excludeNomineeId} is set on update so a row doesn't conflict with itself.
|
||||
*/
|
||||
@Query("SELECT COUNT(n) FROM ClientNominee n "
|
||||
+ "WHERE n.client.id = :clientId "
|
||||
+ "AND n.nomineeClient.id = :nomineeClientId "
|
||||
+ "AND n.accountType = :accountType "
|
||||
+ "AND n.isDeleted = false "
|
||||
+ "AND (:excludeNomineeId IS NULL OR n.id <> :excludeNomineeId)")
|
||||
long countActiveByClientNomineeAndAccountType(@Param("clientId") Long clientId,
|
||||
@Param("nomineeClientId") Long nomineeClientId,
|
||||
@Param("accountType") String accountType,
|
||||
@Param("excludeNomineeId") Long excludeNomineeId);
|
||||
}
|
||||
|
||||
+6
-1
@@ -23,5 +23,10 @@ import org.springframework.data.domain.Pageable;
|
||||
|
||||
public interface SearchingClientRepository {
|
||||
|
||||
Page<SearchedClient> searchByText(String searchText, Pageable pageable, String officeHierarchy, boolean activeOnly);
|
||||
/**
|
||||
* @param legalFormValue null = no filter; 1 = Individual only; 2 = Non-Individual only.
|
||||
* Driven by the legal-form enum on Client (LegalForm.PERSON / .ENTITY).
|
||||
*/
|
||||
Page<SearchedClient> searchByText(String searchText, Pageable pageable, String officeHierarchy, boolean activeOnly,
|
||||
Integer legalFormValue);
|
||||
}
|
||||
|
||||
+9
-1
@@ -52,7 +52,8 @@ public class SearchingClientRepositoryImpl implements SearchingClientRepository
|
||||
private final CriteriaQueryFactory criteriaQueryFactory;
|
||||
|
||||
@Override
|
||||
public Page<SearchedClient> searchByText(String searchText, Pageable pageable, String officeHierarchy, boolean activeOnly) {
|
||||
public Page<SearchedClient> searchByText(String searchText, Pageable pageable, String officeHierarchy, boolean activeOnly,
|
||||
Integer legalFormValue) {
|
||||
/*
|
||||
* this whole thing can be replaced with Spring Data JPA 3+ with a findBy(Specification, Pageable) call but at
|
||||
* this point the upgrade is too costly
|
||||
@@ -85,6 +86,13 @@ public class SearchingClientRepositoryImpl implements SearchingClientRepository
|
||||
predicates.add(cb.equal(r.get("status"), ClientStatus.ACTIVE.getValue()));
|
||||
}
|
||||
|
||||
// Restrict to one legal form when requested. Used by guardian/nominee/family-member
|
||||
// pickers so non-individual entities (companies, partnerships) never appear as
|
||||
// selectable parties — they can never legally hold those relationships.
|
||||
if (legalFormValue != null) {
|
||||
predicates.add(cb.equal(r.get("legalForm"), legalFormValue));
|
||||
}
|
||||
|
||||
return cb.and(predicates.toArray(new Predicate[0]));
|
||||
};
|
||||
criteriaQueryFactory.applySpecificationToCriteria(root, spec, query);
|
||||
|
||||
Reference in New Issue
Block a user