fix(documentmanagement): close leaked file handles causing Windows re-upload failures; add entity KYC document CodeValues
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 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 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 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-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (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-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

FileContentStoreService.upload() opened a FileOutputStream (write) and an
InputStream (post-upload MIME check) without closing either. Windows keeps
those handles open for the JVM's lifetime, so the next upsert-style
delete-then-recreate of the same file (e.g. client signature/photo
re-upload) failed with FileSystemException: "being used by another
process". Wrapped both in try-with-resources.

Also adds every distinct non-individual/constitution KYC document name
(HUF, Private/Public Ltd, LLP, Partnership, Trust, Society, Proprietorship,
Association, Govt/PSU, Cooperative) as a KYCIdProofType code value.
resolveDocumentTypeId() on the frontend matches the picked document name
against this list before creating a client_identifier; since these entity
document names had no corresponding code value, every non-individual
client's KYC document upload silently failed to persist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 11:34:18 +05:30
co-authored by Claude Sonnet 5
parent 6e3f195de0
commit 7c15fbb0c7
3 changed files with 93 additions and 5 deletions
@@ -95,23 +95,24 @@ public class FileContentStoreService implements ContentStoreService {
final var target = Files.exists(getPath(safePath, false)) ? getPath(safePath, false) : getPath(safePath, true);
try (var in = is) {
IOUtils.copy(in, new FileOutputStream(target.toString()));
try (var in = is; var out = new FileOutputStream(target.toString())) {
IOUtils.copy(in, out);
} catch (Exception e) {
throw new ContentStoreException(e);
}
final var relativePath = getRelativePath(target);
try {
postUploadContentPolicy
.check(ContentPolicyContext.builder().path(path).inputStream(getInputStream(target)).mimeType(mimeType).build());
try (var in = getInputStream(target)) {
postUploadContentPolicy.check(ContentPolicyContext.builder().path(path).inputStream(in).mimeType(mimeType).build());
} catch (ContentPolicyException e) {
log.warn("Delete file because it didn't comply with the post-upload policy check: {} - {}", target, e.getMessage());
delete(relativePath.toString());
throw e;
} catch (IOException e) {
throw new ContentStoreException(e);
}
return relativePath.toString();
@@ -452,6 +452,7 @@
<include file="parts/0429_add_client_pending_edit_table.xml" relativeToChangelogFile="true"/>
<include file="parts/0430_add_client_edit_approval_permissions.xml" relativeToChangelogFile="true"/>
<include file="parts/0431_add_original_json_to_client_pending_edit.xml" relativeToChangelogFile="true"/>
<include file="parts/0432_add_entity_kyc_document_type_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
@@ -0,0 +1,86 @@
<?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">
<!-- ClientKycStepComponent's constitutionDocTypes / nonIndividualDefaultDocTypes (frontend-only
maps, one per legal-entity constitution: HUF/Private Ltd/Public Ltd/LLP/Partnership/Trust/
Society/Proprietorship/Association/Govt-PSU/Cooperative) were never backed by a KYCIdProofType
code value the way individual OVDs (0251_add_kyc_proof_type_codes.xml) and minor OVDs
(0428_add_minor_kyc_id_proof_codes.xml) are. Since edit-client/create-client's
saveKycDocProof() -> resolveDocumentTypeId() resolves the picked document NAME against this
code list before creating the client_identifier, every non-individual client's KYC document
upload silently failed to save the identifier/document (no error surfaced — the KYC record's
own document-name/number fields persisted fine via the KYC entity, but the uploaded file
never did, so it vanished on refresh). This adds every distinct entity/constitution document
name used by the frontend as a KYCIdProofType code value so resolution succeeds. -->
<changeSet author="CBS" id="0432-1">
<sql>
INSERT INTO m_code_value (code_id, code_value, code_description, order_position, is_active, is_mandatory)
SELECT c.id, v.code_value, v.code_value,
(SELECT COALESCE(MAX(cv.order_position), 0) FROM m_code_value cv WHERE cv.code_id = c.id)
+ ROW_NUMBER() OVER (ORDER BY v.code_value),
true, false
FROM m_code c
CROSS JOIN (VALUES
('Certificate of Incorporation / Registration Certificate'),
('PAN Card of Entity'),
('Memorandum &amp; Articles of Association / Partnership Deed / Trust Deed'),
('Board Resolution / Authorization Letter'),
('KYC of Authorized Signatory / Power of Attorney Holder'),
('GST Registration Certificate'),
('PAN Card of HUF'),
('Declaration by Karta (with name, address &amp; HUF member details)'),
('KYC of Karta (OVDs + PAN + Photo)'),
('Proof of Address of HUF'),
('Certificate of Incorporation'),
('Memorandum &amp; Articles of Association'),
('PAN of Company'),
('Board Resolution (authorizing signatories)'),
('KYC of Authorized Signatories'),
('LLP Agreement'),
('PAN of LLP'),
('Designated Partners Resolution'),
('KYC of Designated Partners / Authorized Signatories'),
('Registration Certificate'),
('Partnership Deed'),
('PAN of Firm'),
('Trust Deed'),
('PAN of Trust'),
('KYC of Trustees / Authorized Signatories'),
('PAN of Society'),
('Proof of Business Existence (GST Certificate / Shop License)'),
('PAN of Proprietor'),
('Proprietor''s KYC (OVDs + PAN + Photo)'),
('PAN'),
('Resolution authorizing signatories'),
('Government Notification / Order'),
('PAN (if applicable)'),
('Authorized Signatory KYC')
) AS v(code_value)
WHERE c.code_name = 'KYCIdProofType'
AND NOT EXISTS (
SELECT 1 FROM m_code_value cv2 WHERE cv2.code_id = c.id AND cv2.code_value = v.code_value
);
</sql>
</changeSet>
</databaseChangeLog>