Add Client CKYC backend module with all source, migrations, and static resources

Includes CKYC batch processing, KYC proof types, non-CKYC client APIs,
ReKYC handler, SFTP upload service, and DB migrations (0250-0255).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-16 13:42:21 +05:30
co-authored by Claude Opus 4.6
parent 011239f399
commit b4429d04b0
38 changed files with 3103 additions and 12 deletions
@@ -179,9 +179,9 @@ fineract.loan.transactionprocessor.error-not-found-fail=${FINERACT_LOAN_TRANSACT
fineract.loan.status-change-history-statuses=${FINERACT_LOAN_STATUS_CHANGE_HISTORY_STATUSES:NONE}
fineract.content.regex-whitelist-enabled=${FINERACT_CONTENT_REGEX_WHITELIST_ENABLED:true}
fineract.content.regex-whitelist=${FINERACT_CONTENT_REGEX_WHITELIST:.*\\.pdf$,.*\\.doc,.*\\.docx,.*\\.xls,.*\\.xlsx,.*\\.jpg,.*\\.jpeg,.*\\.png}
fineract.content.regex-whitelist=${FINERACT_CONTENT_REGEX_WHITELIST:.*\\.pdf$,.*\\.doc,.*\\.docx,.*\\.xls,.*\\.xlsx,.*\\.jpg,.*\\.jpeg,.*\\.png,.*\\.zip$}
fineract.content.mime-whitelist-enabled=${FINERACT_CONTENT_MIME_WHITELIST_ENABLED:true}
fineract.content.mime-whitelist=${FINERACT_CONTENT_MIME_WHITELIST:application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,image/jpeg,image/png}
fineract.content.mime-whitelist=${FINERACT_CONTENT_MIME_WHITELIST:application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,image/jpeg,image/png,application/zip,application/x-zip-compressed,application/octet-stream}
fineract.content.default-buffer-size=${FINERACT_CONTENT_DEFAULT_BUFFER_SIZE:8192}
fineract.content.filesystem.enabled=${FINERACT_CONTENT_FILESYSTEM_ENABLED:true}
fineract.content.filesystem.rootFolder=${FINERACT_CONTENT_FILESYSTEM_ROOT_FOLDER:${user.home}/.fineract}
@@ -268,4 +268,10 @@
<include file="parts/0247_add_huf_client_classification.xml" relativeToChangelogFile="true"/>
<include file="parts/0248_add_cin_din_verification_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0249_add_non_person_extended_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0250_add_kyc_director_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0251_add_kyc_proof_type_codes.xml" relativeToChangelogFile="true"/>
<include file="parts/0252_add_ckyc_uploaded_on.xml" relativeToChangelogFile="true"/>
<include file="parts/0253_add_ckyc_batch_table.xml" relativeToChangelogFile="true"/>
<include file="parts/0254_fix_ckyc_batch_audit_columns.xml" relativeToChangelogFile="true"/>
<include file="parts/0255_add_ckyc_batch_id_to_client_kyc.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,33 @@
<?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">
<changeSet author="fineract" id="0250-1" context="default">
<addColumn tableName="m_client_kyc">
<column name="is_director" type="boolean" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="din_mca_response" type="longtext"/>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,96 @@
<?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">
<!-- ==================== KYCIdProofType code ==================== -->
<changeSet author="fineract" id="0251-1">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_code WHERE code_name = 'KYCIdProofType'</sqlCheck>
</preConditions>
<insert tableName="m_code">
<column name="code_name" value="KYCIdProofType"/>
<column name="is_system_defined" valueBoolean="true"/>
</insert>
</changeSet>
<changeSet author="fineract" id="0251-2">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_code_value WHERE code_id = (SELECT id FROM m_code WHERE code_name = 'KYCIdProofType')</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_code_value (code_id, code_value, code_description, order_position, is_active, is_mandatory)
SELECT id, val, descr, pos, true, false FROM m_code
CROSS JOIN (VALUES
('PAN Card', 'PAN', 1),
('Aadhaar Card', 'AADHAAR', 2),
('Passport', 'PASSPORT', 3),
('Voter ID Card (EPIC)', 'VOTER_ID', 4),
('Driving License', 'DRIVING_LICENSE', 5),
('NREGA Job Card (signed by government authority)', 'NREGA_JOB_CARD', 6),
('Letter issued by National Population Register (NPR)', 'NPR_LETTER', 7)
) AS t(val, descr, pos)
WHERE code_name = 'KYCIdProofType';
</sql>
</changeSet>
<!-- ==================== KYCAddressProofType code ==================== -->
<changeSet author="fineract" id="0251-3">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_code WHERE code_name = 'KYCAddressProofType'</sqlCheck>
</preConditions>
<insert tableName="m_code">
<column name="code_name" value="KYCAddressProofType"/>
<column name="is_system_defined" valueBoolean="true"/>
</insert>
</changeSet>
<changeSet author="fineract" id="0251-4">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_code_value WHERE code_id = (SELECT id FROM m_code WHERE code_name = 'KYCAddressProofType')</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_code_value (code_id, code_value, code_description, order_position, is_active, is_mandatory)
SELECT id, val, descr, pos, true, false FROM m_code
CROSS JOIN (VALUES
('Aadhaar Card', 'AADHAAR', 1),
('Passport', 'PASSPORT', 2),
('Driving License', 'DRIVING_LICENSE', 3),
('Voter ID Card', 'VOTER_ID', 4),
('Utility Bills', 'UTILITY_BILL', 5),
('Electricity Bill', 'ELECTRICITY_BILL', 6),
('Water Bill', 'WATER_BILL', 7),
('Gas Bill', 'GAS_BILL', 8),
('Internet Bill', 'INTERNET_BILL', 9),
('Bank Account Statement / Passbook', 'BANK_STATEMENT', 10),
('Postpaid Mobile Bill', 'POSTPAID_MOBILE_BILL', 11),
('Registered Rent Agreement', 'RENT_AGREEMENT', 12),
('Property Tax Receipt', 'PROPERTY_TAX_RECEIPT', 13),
('Employer Letter (for salaried employees)', 'EMPLOYER_LETTER', 14),
('Pension Payment Orders (for retirees)', 'PENSION_PAYMENT_ORDER', 15)
) AS t(val, descr, pos)
WHERE code_name = 'KYCAddressProofType';
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,46 @@
<?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">
<!-- Track when the CKYC number was pushed to CERSAI (via SFTP in prod, dummy stub for now) -->
<changeSet author="fineract" id="0252-1">
<preConditions onFail="MARK_RAN">
<not><columnExists tableName="m_client_kyc" columnName="ckyc_uploaded_on"/></not>
</preConditions>
<addColumn tableName="m_client_kyc">
<column name="ckyc_uploaded_on" type="DATE"/>
</addColumn>
</changeSet>
<!-- Optional batch id from the CERSAI SFTP response, useful for reconciliation -->
<changeSet author="fineract" id="0252-2">
<preConditions onFail="MARK_RAN">
<not><columnExists tableName="m_client_kyc" columnName="ckyc_batch_id"/></not>
</preConditions>
<addColumn tableName="m_client_kyc">
<column name="ckyc_batch_id" type="VARCHAR(50)"/>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,84 @@
<?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">
<!--
CERSAI CKYC batch ZIP registry.
One row per ZIP assembled by the bank for CERSAI submission.
The ZIP itself lives in m_document (parent_entity_type='ckyc_batch').
-->
<changeSet author="fineract" id="0253-1">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_ckyc_batch"/></not>
</preConditions>
<createTable tableName="m_ckyc_batch">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="batch_id" type="VARCHAR(50)">
<constraints nullable="false" unique="true"/>
</column>
<column name="filename" type="VARCHAR(250)">
<constraints nullable="false"/>
</column>
<column name="checksum_sha256" type="VARCHAR(80)"/>
<column name="record_count" type="INT" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="size_bytes" type="BIGINT" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="document_id" type="INT">
<constraints nullable="true"/>
</column>
<column name="created_on" type="DATE">
<constraints nullable="false"/>
</column>
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="created_by_id" type="BIGINT"/>
<column name="uploaded_to_cersai" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="uploaded_on" type="DATE"/>
<column name="upload_status" type="VARCHAR(20)"/>
<column name="upload_remarks" type="VARCHAR(500)"/>
<column name="manifest_json" type="CLOB"/>
</createTable>
</changeSet>
<changeSet author="fineract" id="0253-2">
<addForeignKeyConstraint baseTableName="m_ckyc_batch" baseColumnNames="document_id"
referencedTableName="m_document" referencedColumnNames="id"
constraintName="fk_m_ckyc_batch_document"/>
</changeSet>
<changeSet author="fineract" id="0253-3">
<createIndex tableName="m_ckyc_batch" indexName="idx_m_ckyc_batch_created_on">
<column name="created_on"/>
</createIndex>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,46 @@
<?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">
<!--
0253 declared created_on_utc and created_by_id columns as if m_ckyc_batch would use the
Fineract auditable superclass. The entity instead extends AbstractPersistableCustom (no audit
columns), so these columns are unused. created_on_utc was NOT NULL which breaks inserts.
Drop both.
-->
<changeSet author="fineract" id="0254-1">
<preConditions onFail="MARK_RAN">
<columnExists tableName="m_ckyc_batch" columnName="created_on_utc"/>
</preConditions>
<dropColumn tableName="m_ckyc_batch" columnName="created_on_utc"/>
</changeSet>
<changeSet author="fineract" id="0254-2">
<preConditions onFail="MARK_RAN">
<columnExists tableName="m_ckyc_batch" columnName="created_by_id"/>
</preConditions>
<dropColumn tableName="m_ckyc_batch" columnName="created_by_id"/>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,40 @@
<?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">
<!--
Link m_client_kyc rows to the CKYC batch they were shipped in. Populated by the dummy /
real CERSAI SFTP upload service so the Mark Non-CKYC dashboard can hide clients that have
already been bundled + uploaded.
-->
<changeSet author="fineract" id="0255-1">
<preConditions onFail="MARK_RAN">
<not><columnExists tableName="m_client_kyc" columnName="ckyc_batch_id"/></not>
</preConditions>
<addColumn tableName="m_client_kyc">
<column name="ckyc_batch_id" type="VARCHAR(50)"/>
</addColumn>
</changeSet>
</databaseChangeLog>