diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java index 3f1a05f6d..452120d95 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java @@ -406,6 +406,7 @@ public final class ClientData implements Comparable, Serializable { data.degreeQualificationOptions = templateData.degreeQualificationOptions; data.religionOptions = templateData.religionOptions; data.socialCategoryOptions = templateData.socialCategoryOptions; + data.pwdCategoryOptions = templateData.pwdCategoryOptions; data.maritalStatus = clientData.maritalStatus; data.educationLevel = clientData.educationLevel; data.degreeQualification = clientData.degreeQualification; @@ -467,6 +468,7 @@ public final class ClientData implements Comparable, Serializable { data.degreeQualificationOptions = clientData.degreeQualificationOptions; data.religionOptions = clientData.religionOptions; data.socialCategoryOptions = clientData.socialCategoryOptions; + data.pwdCategoryOptions = clientData.pwdCategoryOptions; data.maritalStatus = clientData.maritalStatus; data.educationLevel = clientData.educationLevel; data.degreeQualification = clientData.degreeQualification; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientKycApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientKycApiResource.java index 9b406eec8..ebc4e49a3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientKycApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientKycApiResource.java @@ -160,10 +160,10 @@ public class ClientKycApiResource { template.put("clientTypeName", clientTypeName); template.put("legalFormId", legalFormId); - // Minor detection mirrors the frontend's own rule (age < 10) so the document list + // Minor detection mirrors the frontend's own rule (age < 18) so the document list // served here stays consistent with what the UI computes from the same date of birth. final LocalDate dateOfBirth = client.dateOfBirth(); - final boolean isMinor = dateOfBirth != null && Period.between(dateOfBirth, LocalDate.now()).getYears() < 10; + final boolean isMinor = dateOfBirth != null && Period.between(dateOfBirth, LocalDate.now()).getYears() < 18; template.put("isMinor", isMinor); template.put("requiredDocuments", getRequiredDocuments(clientTypeName, legalFormId, isMinor)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientKycCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientKycCommand.java index 246a47656..b3d5cb840 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientKycCommand.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/command/ClientKycCommand.java @@ -71,6 +71,15 @@ public class ClientKycCommand { } public void validateForInitiate() { + validateForInitiate(false); + } + + /** + * @param skipIdentityDocumentCheck true for minors, who identify via the Birth Certificate / + * School ID document-type picker (a separate upload path) + * rather than PAN/Aadhaar/GSTIN. + */ + public void validateForInitiate(final boolean skipIdentityDocumentCheck) { final List errors = new ArrayList<>(); final DataValidatorBuilder v = new DataValidatorBuilder(errors).resource(ClientKycApiConstants.RESOURCE_NAME); @@ -101,13 +110,16 @@ public class ClientKycCommand { "GSTIN must be a valid 15-character GST Identification Number"); } - // At least one ID document must be provided - final boolean hasPan = panNumber != null && !panNumber.isBlank(); - final boolean hasAadhaar = aadhaarNumberMasked != null && !aadhaarNumberMasked.isBlank(); - final boolean hasGstin = gstinNumber != null && !gstinNumber.isBlank(); - if (!hasPan && !hasAadhaar && !hasGstin) { - v.reset().parameter(ClientKycApiConstants.panNumberParam).value(panNumber) - .notBlank(); + // At least one ID document must be provided — except for minors, who prove identity via + // the Birth Certificate / School ID document-type picker instead. + if (!skipIdentityDocumentCheck) { + final boolean hasPan = panNumber != null && !panNumber.isBlank(); + final boolean hasAadhaar = aadhaarNumberMasked != null && !aadhaarNumberMasked.isBlank(); + final boolean hasGstin = gstinNumber != null && !gstinNumber.isBlank(); + if (!hasPan && !hasAadhaar && !hasGstin) { + v.reset().parameter(ClientKycApiConstants.panNumberParam).value(panNumber) + .notBlank(); + } } throwExceptionIfValidationWarningsExist(errors); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientKycWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientKycWritePlatformServiceJpaRepositoryImpl.java index e1642e84b..bae1084f9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientKycWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientKycWritePlatformServiceJpaRepositoryImpl.java @@ -84,9 +84,13 @@ public class ClientKycWritePlatformServiceJpaRepositoryImpl implements ClientKyc this.context.authenticatedUser(); LOG.info("initiateKyc: clientId={}, requestJson={}", clientId, command.json()); + final Client clientForValidation = clientRepository.findOneWithNotFoundDetection(clientId); + final ClientKycCommand kycCommand = deserializer.commandFromApiJson( command.json(), ClientKycApiConstants.INITIATE_REQUEST_PARAMS); - kycCommand.validateForInitiate(); + // Minors identify via the Birth Certificate / School ID document-type picker (a separate + // upload path), not PAN/Aadhaar/GSTIN — so the "at least one ID" check doesn't apply to them. + kycCommand.validateForInitiate(Boolean.TRUE.equals(clientForValidation.getIsMinor())); // CIN / GSTIN are only applicable for NON_INDIVIDUAL clients final Integer kycTypeOnInitiate = command.integerValueOfParameterNamed("kycType"); @@ -104,7 +108,7 @@ public class ClientKycWritePlatformServiceJpaRepositoryImpl implements ClientKyc } try { - final Client client = clientRepository.findOneWithNotFoundDetection(clientId); + final Client client = clientForValidation; final ClientKyc kyc = ClientKyc.initiateKyc(client, command); kycRepositoryWrapper.saveAndFlush(kyc); @@ -957,7 +961,7 @@ public class ClientKycWritePlatformServiceJpaRepositoryImpl implements ClientKyc public void initiateKycInline(final Client client, final String kycJson) { final ClientKycCommand kycCommand = deserializer.commandFromApiJson( kycJson, ClientKycApiConstants.INITIATE_REQUEST_PARAMS); - kycCommand.validateForInitiate(); + kycCommand.validateForInitiate(Boolean.TRUE.equals(client.getIsMinor())); if (kycRepositoryWrapper.existsByClientId(client.getId())) { LOG.info("KYC record already exists for client {}. Skipping inline initiation.", client.getId()); diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 11778358a..2abef71f9 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -448,6 +448,7 @@ + + + + + + + + SELECT COUNT(*) FROM m_code_value cv JOIN m_code c ON c.id = cv.code_id + WHERE c.code_name = 'KYCIdProofType' AND cv.code_value = 'Birth Certificate' + + + + INSERT INTO m_code_value (code_id, code_value, code_description, order_position, is_active, is_mandatory) + SELECT c.id, 'Birth Certificate', 'BIRTH_CERTIFICATE', + (SELECT COALESCE(MAX(cv.order_position), 0) FROM m_code_value cv WHERE cv.code_id = c.id) + 1, + true, false + FROM m_code c + WHERE c.code_name = 'KYCIdProofType'; + + + + + + + SELECT COUNT(*) FROM m_code_value cv JOIN m_code c ON c.id = cv.code_id + WHERE c.code_name = 'KYCIdProofType' AND cv.code_value = 'School ID Card' + + + + INSERT INTO m_code_value (code_id, code_value, code_description, order_position, is_active, is_mandatory) + SELECT c.id, 'School ID Card', 'SCHOOL_ID_CARD', + (SELECT COALESCE(MAX(cv.order_position), 0) FROM m_code_value cv WHERE cv.code_id = c.id) + 1, + true, false + FROM m_code c + WHERE c.code_name = 'KYCIdProofType'; + + + +