Merge branch 'feature/account-charges' of https://git.zeptic.in/CBSNexora/JAVA-Backend-CBS-MAIN into feature/account-charges
This commit is contained in:
+8
@@ -184,6 +184,14 @@ public class ClientNonPerson extends AbstractPersistableCustom<Long> {
|
|||||||
return constitutionId;
|
return constitutionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CodeValue getConstitution() {
|
||||||
|
return this.constitution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getDateOfRegistration() {
|
||||||
|
return this.dateOfRegistration;
|
||||||
|
}
|
||||||
|
|
||||||
public Long mainBusinessLineId() {
|
public Long mainBusinessLineId() {
|
||||||
Long mainBusinessLineId = null;
|
Long mainBusinessLineId = null;
|
||||||
if (this.mainBusinessLine != null) {
|
if (this.mainBusinessLine != null) {
|
||||||
|
|||||||
+63
-2
@@ -121,6 +121,11 @@ public class LincPayKycVerificationServiceImpl implements KycVerificationService
|
|||||||
final String rawResponse = post(url, requestBody.toString());
|
final String rawResponse = post(url, requestBody.toString());
|
||||||
final JsonObject response = parseJson(rawResponse);
|
final JsonObject response = parseJson(rawResponse);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return KycVerificationResult.failure(
|
||||||
|
"Aadhaar OTP generation failed — vendor returned non-JSON: " + vendorBodySnippet(rawResponse),
|
||||||
|
rawResponse);
|
||||||
|
}
|
||||||
if (isSuccess(response)) {
|
if (isSuccess(response)) {
|
||||||
// LincPay nests reference_id inside "data" object
|
// LincPay nests reference_id inside "data" object
|
||||||
String refNumber = null;
|
String refNumber = null;
|
||||||
@@ -177,6 +182,11 @@ public class LincPayKycVerificationServiceImpl implements KycVerificationService
|
|||||||
final String rawResponse = post(url, requestBody.toString());
|
final String rawResponse = post(url, requestBody.toString());
|
||||||
final JsonObject response = parseJson(rawResponse);
|
final JsonObject response = parseJson(rawResponse);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return KycVerificationResult.failure(
|
||||||
|
"Aadhaar OTP verification failed — vendor returned non-JSON: " + vendorBodySnippet(rawResponse),
|
||||||
|
rawResponse);
|
||||||
|
}
|
||||||
if (isSuccess(response)) {
|
if (isSuccess(response)) {
|
||||||
LOG.info("LincPay Aadhaar OTP verified successfully for refNumber: {}", refNumber);
|
LOG.info("LincPay Aadhaar OTP verified successfully for refNumber: {}", refNumber);
|
||||||
return KycVerificationResult.success(refNumber, rawResponse);
|
return KycVerificationResult.success(refNumber, rawResponse);
|
||||||
@@ -220,6 +230,12 @@ public class LincPayKycVerificationServiceImpl implements KycVerificationService
|
|||||||
LOG.info("LincPay PAN raw response: {}", rawResponse);
|
LOG.info("LincPay PAN raw response: {}", rawResponse);
|
||||||
final JsonObject response = parseJson(rawResponse);
|
final JsonObject response = parseJson(rawResponse);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
// Vendor returned non-JSON (e.g. "Invalid DOB format. Required format is dd/MM/yyyy")
|
||||||
|
return KycVerificationResult.failure(
|
||||||
|
"PAN verification failed — vendor returned non-JSON: " + vendorBodySnippet(rawResponse),
|
||||||
|
rawResponse);
|
||||||
|
}
|
||||||
if (isSuccess(response)) {
|
if (isSuccess(response)) {
|
||||||
LOG.info("LincPay PAN verified successfully for PAN: {}", panNumber);
|
LOG.info("LincPay PAN verified successfully for PAN: {}", panNumber);
|
||||||
return KycVerificationResult.success("PAN-" + panNumber, rawResponse);
|
return KycVerificationResult.success("PAN-" + panNumber, rawResponse);
|
||||||
@@ -259,6 +275,11 @@ public class LincPayKycVerificationServiceImpl implements KycVerificationService
|
|||||||
final String rawResponse = post(url, requestBody.toString());
|
final String rawResponse = post(url, requestBody.toString());
|
||||||
final JsonObject response = parseJson(rawResponse);
|
final JsonObject response = parseJson(rawResponse);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return KycVerificationResult.failure(
|
||||||
|
"GSTIN verification failed — vendor returned non-JSON: " + vendorBodySnippet(rawResponse),
|
||||||
|
rawResponse);
|
||||||
|
}
|
||||||
if (isSuccess(response)) {
|
if (isSuccess(response)) {
|
||||||
LOG.info("LincPay GSTIN verified successfully for GSTIN: {}", gstinNumber);
|
LOG.info("LincPay GSTIN verified successfully for GSTIN: {}", gstinNumber);
|
||||||
return KycVerificationResult.success("GSTN-" + gstinNumber, rawResponse);
|
return KycVerificationResult.success("GSTN-" + gstinNumber, rawResponse);
|
||||||
@@ -300,6 +321,11 @@ public class LincPayKycVerificationServiceImpl implements KycVerificationService
|
|||||||
LOG.info("LincPay CIN raw response: {}", rawResponse);
|
LOG.info("LincPay CIN raw response: {}", rawResponse);
|
||||||
final JsonObject response = parseJson(rawResponse);
|
final JsonObject response = parseJson(rawResponse);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return KycVerificationResult.failure(
|
||||||
|
"CIN verification failed — vendor returned non-JSON: " + vendorBodySnippet(rawResponse),
|
||||||
|
rawResponse);
|
||||||
|
}
|
||||||
if (isSuccess(response)) {
|
if (isSuccess(response)) {
|
||||||
LOG.info("LincPay CIN verified successfully for CIN: {}", cinNumber);
|
LOG.info("LincPay CIN verified successfully for CIN: {}", cinNumber);
|
||||||
return KycVerificationResult.success("CIN-" + cinNumber, rawResponse);
|
return KycVerificationResult.success("CIN-" + cinNumber, rawResponse);
|
||||||
@@ -341,6 +367,11 @@ public class LincPayKycVerificationServiceImpl implements KycVerificationService
|
|||||||
LOG.info("LincPay DIN raw response: {}", rawResponse);
|
LOG.info("LincPay DIN raw response: {}", rawResponse);
|
||||||
final JsonObject response = parseJson(rawResponse);
|
final JsonObject response = parseJson(rawResponse);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return KycVerificationResult.failure(
|
||||||
|
"DIN verification failed — vendor returned non-JSON: " + vendorBodySnippet(rawResponse),
|
||||||
|
rawResponse);
|
||||||
|
}
|
||||||
if (isSuccess(response)) {
|
if (isSuccess(response)) {
|
||||||
LOG.info("LincPay DIN verified successfully for DIN: {}", dinNumber);
|
LOG.info("LincPay DIN verified successfully for DIN: {}", dinNumber);
|
||||||
return KycVerificationResult.success("DIN-" + dinNumber, rawResponse);
|
return KycVerificationResult.success("DIN-" + dinNumber, rawResponse);
|
||||||
@@ -374,12 +405,42 @@ public class LincPayKycVerificationServiceImpl implements KycVerificationService
|
|||||||
return response.getBody();
|
return response.getBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse LincPay's response defensively. The vendor sometimes returns plain text instead of
|
||||||
|
* JSON (e.g. "Invalid DOB format. Required format is dd/MM/yyyy" when the input is missing
|
||||||
|
* a field, or an HTML error page when the upstream service is down). In those cases we
|
||||||
|
* used to throw MalformedJsonException at column 10 with no clue what the body actually
|
||||||
|
* contained — now we return null and let the caller surface the raw text.
|
||||||
|
*/
|
||||||
private JsonObject parseJson(final String raw) {
|
private JsonObject parseJson(final String raw) {
|
||||||
if (raw == null || raw.isBlank()) {
|
if (raw == null || raw.isBlank()) {
|
||||||
return new JsonObject();
|
return new JsonObject();
|
||||||
}
|
}
|
||||||
final JsonElement parsed = JsonParser.parseString(raw);
|
final String trimmed = raw.trim();
|
||||||
return parsed.isJsonObject() ? parsed.getAsJsonObject() : new JsonObject();
|
final char first = trimmed.charAt(0);
|
||||||
|
if (first != '{' && first != '[') {
|
||||||
|
// Not even shaped like JSON — vendor returned plain text or HTML.
|
||||||
|
LOG.warn("LincPay returned non-JSON body (first 200 chars): {}",
|
||||||
|
trimmed.substring(0, Math.min(200, trimmed.length())));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final JsonElement parsed = JsonParser.parseString(trimmed);
|
||||||
|
return parsed.isJsonObject() ? parsed.getAsJsonObject() : new JsonObject();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.warn("LincPay JSON parse failed: {} | raw body: {}", e.getMessage(),
|
||||||
|
trimmed.substring(0, Math.min(200, trimmed.length())));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build a clean failure message that includes the actual vendor text (truncated) for ops debugging. */
|
||||||
|
private String vendorBodySnippet(final String raw) {
|
||||||
|
if (raw == null) {
|
||||||
|
return "(empty body)";
|
||||||
|
}
|
||||||
|
final String t = raw.trim();
|
||||||
|
return t.isEmpty() ? "(empty body)" : t.substring(0, Math.min(200, t.length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+24
@@ -112,6 +112,25 @@ public class ClientFamilyMembersWritePlatformServiceImpl implements ClientFamily
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Family members are an individual-client concept (mother/father/spouse etc.). Non-individual
|
||||||
|
* entities — companies, trusts, LLPs, even proprietorships — don't have "family" semantics;
|
||||||
|
* any relational role they need is handled via nominees + authorized signatories instead.
|
||||||
|
* Refuse the add/update so we don't end up with stray rows on entity clients.
|
||||||
|
*/
|
||||||
|
private void assertCanHaveFamilyMembers(final Client client) {
|
||||||
|
if (client == null || client.getLegalForm() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (org.apache.fineract.portfolio.client.domain.LegalForm.fromInt(client.getLegalForm()).isEntity()) {
|
||||||
|
throw new PlatformApiDataValidationException(java.util.List.of(
|
||||||
|
ApiParameterError.parameterError(
|
||||||
|
"error.msg.client.family.member.not.allowed.for.non.individual",
|
||||||
|
"Family members are not allowed for non-individual clients.",
|
||||||
|
"legalForm", client.getLegalForm())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult addFamilyMember(final long clientId, final JsonCommand command) {
|
public CommandProcessingResult addFamilyMember(final long clientId, final JsonCommand command) {
|
||||||
|
|
||||||
@@ -139,6 +158,7 @@ public class ClientFamilyMembersWritePlatformServiceImpl implements ClientFamily
|
|||||||
apiJsonDeserializer.validateForCreate(clientId, command.json());
|
apiJsonDeserializer.validateForCreate(clientId, command.json());
|
||||||
|
|
||||||
Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
||||||
|
assertCanHaveFamilyMembers(client);
|
||||||
firstName = command.stringValueOfParameterNamed("firstName");
|
firstName = command.stringValueOfParameterNamed("firstName");
|
||||||
middleName = command.stringValueOfParameterNamed("middleName");
|
middleName = command.stringValueOfParameterNamed("middleName");
|
||||||
lastName = command.stringValueOfParameterNamed("lastName");
|
lastName = command.stringValueOfParameterNamed("lastName");
|
||||||
@@ -203,6 +223,8 @@ public class ClientFamilyMembersWritePlatformServiceImpl implements ClientFamily
|
|||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult addClientFamilyMember(final Client client, final JsonCommand command) {
|
public CommandProcessingResult addClientFamilyMember(final Client client, final JsonCommand command) {
|
||||||
|
|
||||||
|
assertCanHaveFamilyMembers(client);
|
||||||
|
|
||||||
Long relationshipId = null;
|
Long relationshipId = null;
|
||||||
CodeValue relationship = null;
|
CodeValue relationship = null;
|
||||||
CodeValue maritalStatus = null;
|
CodeValue maritalStatus = null;
|
||||||
@@ -394,6 +416,8 @@ public class ClientFamilyMembersWritePlatformServiceImpl implements ClientFamily
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
ClientFamilyMembers clientFamilyMember = clientFamilyRepository.getReferenceById(familyMemberId);
|
ClientFamilyMembers clientFamilyMember = clientFamilyRepository.getReferenceById(familyMemberId);
|
||||||
|
// Reject updates if the parent client is non-individual — same rule as add.
|
||||||
|
assertCanHaveFamilyMembers(clientFamilyMember.getClient());
|
||||||
|
|
||||||
// Client
|
// Client
|
||||||
// client=clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
// client=clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
||||||
|
|||||||
+37
-14
@@ -43,7 +43,10 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
|||||||
import org.apache.fineract.portfolio.client.domain.ClientKyc;
|
import org.apache.fineract.portfolio.client.domain.ClientKyc;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientKycRepositoryWrapper;
|
import org.apache.fineract.portfolio.client.domain.ClientKycRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientKycStatus;
|
import org.apache.fineract.portfolio.client.domain.ClientKycStatus;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientNonPerson;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientNonPersonRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.LegalForm;
|
||||||
import org.apache.fineract.portfolio.client.exception.ClientKycAlreadyExistsException;
|
import org.apache.fineract.portfolio.client.exception.ClientKycAlreadyExistsException;
|
||||||
import org.apache.fineract.portfolio.client.exception.ClientKycInvalidStateTransitionException;
|
import org.apache.fineract.portfolio.client.exception.ClientKycInvalidStateTransitionException;
|
||||||
import org.apache.fineract.portfolio.client.kyc.KycVerificationResult;
|
import org.apache.fineract.portfolio.client.kyc.KycVerificationResult;
|
||||||
@@ -64,6 +67,7 @@ public class ClientKycWritePlatformServiceJpaRepositoryImpl implements ClientKyc
|
|||||||
|
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final ClientRepositoryWrapper clientRepository;
|
private final ClientRepositoryWrapper clientRepository;
|
||||||
|
private final ClientNonPersonRepositoryWrapper clientNonPersonRepository;
|
||||||
private final ClientKycRepositoryWrapper kycRepositoryWrapper;
|
private final ClientKycRepositoryWrapper kycRepositoryWrapper;
|
||||||
private final ClientKycCommandFromApiJsonDeserializer deserializer;
|
private final ClientKycCommandFromApiJsonDeserializer deserializer;
|
||||||
private final KycVerificationService kycVerificationService;
|
private final KycVerificationService kycVerificationService;
|
||||||
@@ -344,21 +348,40 @@ public class ClientKycWritePlatformServiceJpaRepositoryImpl implements ClientKyc
|
|||||||
"PAN number is not set on this KYC record. Update the KYC record first.");
|
"PAN number is not set on this KYC record. Update the KYC record first.");
|
||||||
}
|
}
|
||||||
|
|
||||||
final String dob = client.dateOfBirth() != null
|
// Non-individual clients store their identity in different fields than individuals:
|
||||||
? client.dateOfBirth().format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
|
// firstname/middlename/lastname ← individual columns; NULL for entities
|
||||||
: null;
|
// fullname ← the entity name lives here
|
||||||
// Build name without salutation: firstname + middlename (if not blank) + lastname
|
// dateOfBirth ← only set for individuals
|
||||||
final StringBuilder panName = new StringBuilder();
|
// ClientNonPerson.dateOfRegistration ← the incorporation date for entities
|
||||||
if (client.getFirstname() != null && !client.getFirstname().isBlank()) {
|
// LincPay's /validate-pan rejects empty/missing DOB with a PLAIN-TEXT "Invalid DOB format"
|
||||||
panName.append(client.getFirstname().trim());
|
// response (not JSON) — Gson then throws MalformedJsonException at column 10. Build the
|
||||||
|
// right (name, dob) pair per legal form so we always send LincPay the data it expects.
|
||||||
|
final String clientNameForPan;
|
||||||
|
final String dob;
|
||||||
|
if (client.getLegalForm() != null && LegalForm.fromInt(client.getLegalForm()).isEntity()) {
|
||||||
|
clientNameForPan = client.getFullname() != null ? client.getFullname().trim() : "";
|
||||||
|
final ClientNonPerson np = this.clientNonPersonRepository.findOneByClientId(client.getId());
|
||||||
|
final java.time.LocalDate regDate = (np != null) ? np.getDateOfRegistration() : null;
|
||||||
|
dob = regDate != null
|
||||||
|
? regDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
|
||||||
|
: null;
|
||||||
|
} else {
|
||||||
|
dob = client.dateOfBirth() != null
|
||||||
|
? client.dateOfBirth().format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
|
||||||
|
: null;
|
||||||
|
// Build name without salutation: firstname + middlename (if not blank) + lastname
|
||||||
|
final StringBuilder panName = new StringBuilder();
|
||||||
|
if (client.getFirstname() != null && !client.getFirstname().isBlank()) {
|
||||||
|
panName.append(client.getFirstname().trim());
|
||||||
|
}
|
||||||
|
if (client.getMiddlename() != null && !client.getMiddlename().isBlank()) {
|
||||||
|
panName.append(" ").append(client.getMiddlename().trim());
|
||||||
|
}
|
||||||
|
if (client.getLastname() != null && !client.getLastname().isBlank()) {
|
||||||
|
panName.append(" ").append(client.getLastname().trim());
|
||||||
|
}
|
||||||
|
clientNameForPan = panName.toString().trim();
|
||||||
}
|
}
|
||||||
if (client.getMiddlename() != null && !client.getMiddlename().isBlank()) {
|
|
||||||
panName.append(" ").append(client.getMiddlename().trim());
|
|
||||||
}
|
|
||||||
if (client.getLastname() != null && !client.getLastname().isBlank()) {
|
|
||||||
panName.append(" ").append(client.getLastname().trim());
|
|
||||||
}
|
|
||||||
final String clientNameForPan = panName.toString().trim();
|
|
||||||
LOG.info("verifyPan: calling LincPay with panNumber='{}', name='{}', dob='{}'",
|
LOG.info("verifyPan: calling LincPay with panNumber='{}', name='{}', dob='{}'",
|
||||||
kyc.getPanNumber(), clientNameForPan, dob);
|
kyc.getPanNumber(), clientNameForPan, dob);
|
||||||
|
|
||||||
|
|||||||
+37
@@ -28,14 +28,19 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||||
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientNominee;
|
import org.apache.fineract.portfolio.client.domain.ClientNominee;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientNomineeRepository;
|
import org.apache.fineract.portfolio.client.domain.ClientNomineeRepository;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientNonPerson;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientNonPersonRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.LegalForm;
|
||||||
import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
|
import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
@@ -58,12 +63,42 @@ public class ClientNomineeWritePlatformServiceImpl implements ClientNomineeWrite
|
|||||||
private final CodeValueRepositoryWrapper codeValueRepository;
|
private final CodeValueRepositoryWrapper codeValueRepository;
|
||||||
private final SavingsAccountRepository savingsAccountRepository;
|
private final SavingsAccountRepository savingsAccountRepository;
|
||||||
private final LoanRepository loanRepository;
|
private final LoanRepository loanRepository;
|
||||||
|
private final ClientNonPersonRepositoryWrapper clientNonPersonRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nominees apply to individual clients and to non-individual proprietorships only. Other
|
||||||
|
* non-individual constitutions (company, LLP, trust, society, HUF, partnership, etc.) don't
|
||||||
|
* carry a nomination concept — succession is governed by their constitutional documents and
|
||||||
|
* authorized signatories. Refuse the add/update so we don't end up with stray rows.
|
||||||
|
*/
|
||||||
|
private void assertCanHaveNominees(final Client client) {
|
||||||
|
if (client == null || client.getLegalForm() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (LegalForm.fromInt(client.getLegalForm()).isPerson()) {
|
||||||
|
return; // individual — always allowed
|
||||||
|
}
|
||||||
|
// Non-individual: allow only if constitution is proprietorship.
|
||||||
|
final ClientNonPerson nonPerson = this.clientNonPersonRepository.findOneByClientId(client.getId());
|
||||||
|
final String constitutionLabel = (nonPerson != null && nonPerson.getConstitution() != null)
|
||||||
|
? nonPerson.getConstitution().getLabel() : null;
|
||||||
|
final String normalized = constitutionLabel == null ? "" : constitutionLabel.toUpperCase().replace(" ", "");
|
||||||
|
if (!"PROPRIETORSHIP".equals(normalized) && !"PROPRIETOR".equals(normalized)) {
|
||||||
|
throw new PlatformApiDataValidationException(java.util.List.of(
|
||||||
|
ApiParameterError.parameterError(
|
||||||
|
"error.msg.client.nominee.not.allowed.for.non.individual",
|
||||||
|
"Nominees are only allowed for individual clients and proprietorship entities. "
|
||||||
|
+ "Constitution: " + (constitutionLabel != null ? constitutionLabel : "(none)"),
|
||||||
|
"legalForm", client.getLegalForm())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public CommandProcessingResult addNominee(final Long clientId, final JsonCommand command) {
|
public CommandProcessingResult addNominee(final Long clientId, final JsonCommand command) {
|
||||||
this.context.authenticatedUser();
|
this.context.authenticatedUser();
|
||||||
final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
|
final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
|
||||||
|
assertCanHaveNominees(client);
|
||||||
|
|
||||||
// ── Account linkage (MANDATORY) ──
|
// ── Account linkage (MANDATORY) ──
|
||||||
final String accountNo = command.stringValueOfParameterNamed("accountNo");
|
final String accountNo = command.stringValueOfParameterNamed("accountNo");
|
||||||
@@ -142,6 +177,8 @@ public class ClientNomineeWritePlatformServiceImpl implements ClientNomineeWrite
|
|||||||
@Transactional
|
@Transactional
|
||||||
public CommandProcessingResult updateNominee(final Long clientId, final Long nomineeId, final JsonCommand command) {
|
public CommandProcessingResult updateNominee(final Long clientId, final Long nomineeId, final JsonCommand command) {
|
||||||
this.context.authenticatedUser();
|
this.context.authenticatedUser();
|
||||||
|
// Same constitution rule as add — refuse updates on entities other than proprietorship.
|
||||||
|
assertCanHaveNominees(this.clientRepository.findOneWithNotFoundDetection(clientId));
|
||||||
|
|
||||||
final ClientNominee nominee = nomineeRepository.findByClientIdAndId(clientId, nomineeId)
|
final ClientNominee nominee = nomineeRepository.findByClientIdAndId(clientId, nomineeId)
|
||||||
.orElseThrow(() -> new ClientNotFoundException(nomineeId));
|
.orElseThrow(() -> new ClientNotFoundException(nomineeId));
|
||||||
|
|||||||
+1
@@ -718,6 +718,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
|
|||||||
builder.append("c.nationality as nationality, ");
|
builder.append("c.nationality as nationality, ");
|
||||||
builder.append("c.is_minor as isMinor, ");
|
builder.append("c.is_minor as isMinor, ");
|
||||||
builder.append("c.guardian_client_id as guardianClientId, ");
|
builder.append("c.guardian_client_id as guardianClientId, ");
|
||||||
|
builder.append("gc.display_name as guardianClientDisplayName, ");
|
||||||
builder.append("c.guardian_relation as guardianRelation, ");
|
builder.append("c.guardian_relation as guardianRelation, ");
|
||||||
builder.append("c.alternate_mobile_no as alternateMobileNo, ");
|
builder.append("c.alternate_mobile_no as alternateMobileNo, ");
|
||||||
builder.append("c.dip_verified as dipVerified, ");
|
builder.append("c.dip_verified as dipVerified, ");
|
||||||
|
|||||||
Reference in New Issue
Block a user