feat: non-individual entity UI improvements — hide individual-only sections, fix legal form display, send extended fields to API

- Hide Family Members tab/section for non-individual entities in client view, create flow, and preview
- Hide Financial & Classification section for non-individual entities in personal data and preview
- Fix legal form column in clients list — fetch legalForm from client API for correct display
- Add person/business icon before client name in list rows
- Remove unsupportedNpd filter — send authorizedSignatory, additionalInfo, dateOfRegistration to API
- Add registration number format hint (mat-hint) based on constitution type
- Add spacing between verification row and constitution type dropdowns
- Reduce table cell padding and prevent action buttons from being clipped
- Add missing MPL-2.0 license headers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-04 15:10:51 +05:30
co-authored by Claude Opus 4.6
parent ab01111424
commit b890d24bb7
522 changed files with 51991 additions and 21885 deletions
@@ -0,0 +1,140 @@
/**
* CBS Nexora — Entity KYC REST Controller
* Package: org.apache.fineract.portfolio.client.api
*
* Endpoints:
* POST /clients/{clientId}/entity-kyc — Create or update entity KYC + MSME
* GET /clients/{clientId}/entity-kyc — Retrieve entity KYC data
* DELETE /clients/{clientId}/entity-kyc — Delete entity KYC data
*/
package org.apache.fineract.portfolio.client.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.client.domain.*;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Path("/v1/clients/{clientId}/entity-kyc")
@Component
@Tag(name = "Client Entity KYC", description = "Non-Individual client MSME & KYC management")
@RequiredArgsConstructor
public class ClientEntityKycApiResource {
private final ClientRepositoryWrapper clientRepository;
private final ClientEntityKycRepository entityKycRepository;
private final FromJsonHelper fromJsonHelper;
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Transactional
@Operation(summary = "Create or Update Entity KYC", description = "Save MSME details, entity KYC document, signatory KYC, authorization document, and risk assessment")
public Response saveEntityKyc(
@PathParam("clientId") Long clientId,
String jsonBody) {
final Client client = clientRepository.findOneWithNotFoundDetection(clientId);
final JsonObject json = fromJsonHelper.parse(jsonBody).getAsJsonObject();
final Map<String, Object> data = toMap(json);
Optional<ClientEntityKyc> existing = entityKycRepository.findByClientId(clientId);
ClientEntityKyc entityKyc;
if (existing.isPresent()) {
entityKyc = existing.get();
entityKyc.update(data);
} else {
String constitutionType = json.has("constitutionType") ? json.get("constitutionType").getAsString() : null;
entityKyc = ClientEntityKyc.create(client, constitutionType);
entityKyc.update(data);
}
entityKycRepository.saveAndFlush(entityKyc);
return Response.ok(Map.of(
"resourceId", entityKyc.getId(),
"clientId", clientId
)).build();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Retrieve Entity KYC", description = "Get MSME, signatory, authorization, and risk data for a non-individual client")
public Response getEntityKyc(@PathParam("clientId") Long clientId) {
clientRepository.findOneWithNotFoundDetection(clientId);
Optional<ClientEntityKyc> entityKyc = entityKycRepository.findByClientId(clientId);
if (entityKyc.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "No entity KYC found for client " + clientId))
.build();
}
ClientEntityKyc kyc = entityKyc.get();
Map<String, Object> result = new HashMap<>();
result.put("id", kyc.getId());
result.put("clientId", clientId);
result.put("constitutionType", kyc.getConstitutionType());
result.put("isMsme", kyc.getIsMsme());
result.put("udyamNumber", kyc.getUdyamNumber());
result.put("enterpriseName", kyc.getEnterpriseName());
result.put("businessSector", kyc.getBusinessSector());
result.put("msmeTurnover", kyc.getMsmeTurnover());
result.put("msmeInvestment", kyc.getMsmeInvestment());
result.put("msmeType", kyc.getMsmeType());
result.put("entityKycDocType", kyc.getEntityKycDocType());
result.put("entityKycDocNumber", kyc.getEntityKycDocNumber());
result.put("entityKycDocIssueDate", kyc.getEntityKycDocIssueDate());
result.put("entityKycDocExpiryDate", kyc.getEntityKycDocExpiryDate());
result.put("signatoryName", kyc.getSignatoryName());
result.put("signatoryDesignation", kyc.getSignatoryDesignation());
result.put("signatoryPan", kyc.getSignatoryPan());
result.put("signatoryAadhaarMasked", kyc.getSignatoryAadhaarMasked());
result.put("authorizationDocType", kyc.getAuthorizationDocType());
result.put("authorizationDocNumber", kyc.getAuthorizationDocNumber());
result.put("authorizationDate", kyc.getAuthorizationDate());
result.put("entityRiskCategory", kyc.getEntityRiskCategory());
result.put("entityRemarks", kyc.getEntityRemarks());
return Response.ok(result).build();
}
@DELETE
@Produces(MediaType.APPLICATION_JSON)
@Transactional
@Operation(summary = "Delete Entity KYC")
public Response deleteEntityKyc(@PathParam("clientId") Long clientId) {
clientRepository.findOneWithNotFoundDetection(clientId);
entityKycRepository.deleteByClientId(clientId);
return Response.ok(Map.of("clientId", clientId, "deleted", true)).build();
}
private Map<String, Object> toMap(JsonObject json) {
Map<String, Object> map = new HashMap<>();
json.entrySet().forEach(e -> {
if (e.getValue().isJsonNull()) {
map.put(e.getKey(), null);
} else if (e.getValue().isJsonPrimitive()) {
var prim = e.getValue().getAsJsonPrimitive();
if (prim.isBoolean()) map.put(e.getKey(), prim.getAsBoolean());
else if (prim.isNumber()) {
if (prim.getAsString().contains(".")) map.put(e.getKey(), prim.getAsBigDecimal());
else map.put(e.getKey(), prim.getAsInt());
}
else map.put(e.getKey(), prim.getAsString());
}
});
return map;
}
}