fix(clients): pwdCategoryOptions on edit template, minor-age threshold to 18, minor KYC identity-proof bypass
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (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 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-2) (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-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
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (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 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-2) (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-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
- ClientData.templateOnTop()/templateWithSavingAccountOptions() never copied pwdCategoryOptions
from the template — the Disability Type dropdown was always empty on the edit-mode GET
/clients/{id}?template=true call, even though create-time template() had it.
- Raise the minor-detection threshold in ClientKycApiResource from age <10 to <18 (legal
definition), mirroring the frontend's own rule so the required-documents list served here
stays consistent.
- ClientKycCommand.validateForInitiate() unconditionally required at least one of
PAN/Aadhaar/GSTIN, with no awareness of the Birth Certificate/School ID document-type picker
minors actually use — blocking "Save & Next" on the KYC tab for every minor client. Added a
skipIdentityDocumentCheck flag, set from client.getIsMinor() at both call sites
(initiateKyc/initiateKycInline).
- Added Birth Certificate and School ID Card as KYCIdProofType code values — they were already
offered as document-type options for minors but never existed in the code-value list that
saveKycDocProof() resolves against, so selecting either silently failed to save any
identifier/document.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -406,6 +406,7 @@ public final class ClientData implements Comparable<ClientData>, 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<ClientData>, 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;
|
||||
|
||||
+2
-2
@@ -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));
|
||||
|
||||
+19
-7
@@ -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<ApiParameterError> 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);
|
||||
|
||||
+7
-3
@@ -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());
|
||||
|
||||
@@ -448,6 +448,7 @@
|
||||
<include file="parts/0425_add_diploma_degree_qualification.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0426_add_graduate_education_level.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0427_add_caretaker_relationship_value.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0428_add_minor_kyc_id_proof_codes.xml" relativeToChangelogFile="true"/>
|
||||
<!-- NOTE: the numbers below (0341-0359) re-use prefixes already used above by the chequebook/CTS
|
||||
clearing catchup migrations — these come from a long-diverged branch (uate) that assigned its own
|
||||
sequence independently. Liquibase tracks changesets by filename + id + author, not by the numeric
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.13.xsd">
|
||||
|
||||
<!-- ClientKycApiResource.getRequiredDocuments() has always offered "Birth Certificate" and
|
||||
"School ID Card" as identity-proof options for minor clients, but neither was ever added
|
||||
as a KYCIdProofType code value (0251_add_kyc_proof_type_codes.xml). Since
|
||||
edit-client/create-client's saveKycDocProof() resolves the picked document type against
|
||||
this code list (resolveDocumentTypeId), selecting either option for a minor silently
|
||||
failed to save any identifier/document — the KYC record's own document-name/number fields
|
||||
persisted fine, but the uploaded file never did. -->
|
||||
<changeSet author="CBS" id="0428-1">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">
|
||||
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'
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
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';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="CBS" id="0428-2">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">
|
||||
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'
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
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';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user