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:
co-authored by
Claude Opus 4.6
parent
ab01111424
commit
b890d24bb7
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* CBS Nexora — India Address Domain Object
|
||||
* Package: org.apache.fineract.portfolio.client.domain
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_client_address_india", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "client_id", "address_type" })
|
||||
})
|
||||
public class ClientAddressIndia {
|
||||
|
||||
public enum AddressType {
|
||||
PERMANENT, CURRENT, OFFICE, CORRESPONDENCE
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "client_id", nullable = false)
|
||||
private Client client;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "address_type", nullable = false, length = 20)
|
||||
private AddressType addressType;
|
||||
|
||||
@Column(name = "address_line_1", length = 500)
|
||||
private String addressLine1;
|
||||
|
||||
@Column(name = "address_line_2", length = 500)
|
||||
private String addressLine2;
|
||||
|
||||
@Column(name = "pin_code", length = 6)
|
||||
private String pinCode;
|
||||
|
||||
@Column(name = "locality", length = 200)
|
||||
private String locality;
|
||||
|
||||
@Column(name = "city", length = 200)
|
||||
private String city;
|
||||
|
||||
@Column(name = "district", length = 200)
|
||||
private String district;
|
||||
|
||||
@Column(name = "state", length = 200)
|
||||
private String state;
|
||||
|
||||
@Column(name = "country", length = 100)
|
||||
private String country = "India";
|
||||
|
||||
@Column(name = "landmark", length = 300)
|
||||
private String landmark;
|
||||
|
||||
// Permanent-specific
|
||||
@Column(name = "proof_type", length = 100)
|
||||
private String proofType;
|
||||
|
||||
@Column(name = "proof_doc_number", length = 100)
|
||||
private String proofDocNumber;
|
||||
|
||||
// Current-specific
|
||||
@Column(name = "residence_type", length = 50)
|
||||
private String residenceType;
|
||||
|
||||
@Column(name = "years_at_address")
|
||||
private Integer yearsAtAddress;
|
||||
|
||||
@Column(name = "same_as_permanent")
|
||||
private Boolean sameAsPermanent = false;
|
||||
|
||||
// Office-specific
|
||||
@Column(name = "org_name", length = 300)
|
||||
private String orgName;
|
||||
|
||||
@Column(name = "department", length = 200)
|
||||
private String department;
|
||||
|
||||
@Column(name = "office_phone", length = 20)
|
||||
private String officePhone;
|
||||
|
||||
@Column(name = "employment_type", length = 100)
|
||||
private String employmentType;
|
||||
|
||||
@Column(name = "employee_id", length = 50)
|
||||
private String employeeId;
|
||||
|
||||
@Column(name = "years_with_org")
|
||||
private Integer yearsWithOrg;
|
||||
|
||||
// Correspondence-specific
|
||||
@Column(name = "comm_mode", length = 50)
|
||||
private String commMode;
|
||||
|
||||
@Column(name = "contact_number", length = 20)
|
||||
private String contactNumber;
|
||||
|
||||
// Audit
|
||||
@Column(name = "created_date", updatable = false)
|
||||
private LocalDateTime createdDate = LocalDateTime.now();
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private LocalDateTime lastModifiedDate = LocalDateTime.now();
|
||||
|
||||
protected ClientAddressIndia() {}
|
||||
|
||||
public static ClientAddressIndia create(Client client, AddressType type) {
|
||||
ClientAddressIndia addr = new ClientAddressIndia();
|
||||
addr.client = client;
|
||||
addr.addressType = type;
|
||||
return addr;
|
||||
}
|
||||
|
||||
public void update(java.util.Map<String, Object> data) {
|
||||
if (data.containsKey("addressLine1")) this.addressLine1 = (String) data.get("addressLine1");
|
||||
if (data.containsKey("addressLine2")) this.addressLine2 = (String) data.get("addressLine2");
|
||||
if (data.containsKey("pinCode")) this.pinCode = (String) data.get("pinCode");
|
||||
if (data.containsKey("locality")) this.locality = (String) data.get("locality");
|
||||
if (data.containsKey("city")) this.city = (String) data.get("city");
|
||||
if (data.containsKey("district")) this.district = (String) data.get("district");
|
||||
if (data.containsKey("state")) this.state = (String) data.get("state");
|
||||
if (data.containsKey("country")) this.country = (String) data.get("country");
|
||||
if (data.containsKey("landmark")) this.landmark = (String) data.get("landmark");
|
||||
if (data.containsKey("proofType")) this.proofType = (String) data.get("proofType");
|
||||
if (data.containsKey("proofDocNumber")) this.proofDocNumber = (String) data.get("proofDocNumber");
|
||||
if (data.containsKey("residenceType")) this.residenceType = (String) data.get("residenceType");
|
||||
if (data.containsKey("yearsAtAddress")) this.yearsAtAddress = toInt(data.get("yearsAtAddress"));
|
||||
if (data.containsKey("sameAsPermanent")) this.sameAsPermanent = (Boolean) data.get("sameAsPermanent");
|
||||
if (data.containsKey("orgName")) this.orgName = (String) data.get("orgName");
|
||||
if (data.containsKey("department")) this.department = (String) data.get("department");
|
||||
if (data.containsKey("officePhone")) this.officePhone = (String) data.get("officePhone");
|
||||
if (data.containsKey("employmentType")) this.employmentType = (String) data.get("employmentType");
|
||||
if (data.containsKey("employeeId")) this.employeeId = (String) data.get("employeeId");
|
||||
if (data.containsKey("yearsWithOrg")) this.yearsWithOrg = toInt(data.get("yearsWithOrg"));
|
||||
if (data.containsKey("commMode")) this.commMode = (String) data.get("commMode");
|
||||
if (data.containsKey("contactNumber")) this.contactNumber = (String) data.get("contactNumber");
|
||||
this.lastModifiedDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
private Integer toInt(Object val) {
|
||||
if (val == null) return null;
|
||||
if (val instanceof Integer) return (Integer) val;
|
||||
return Integer.parseInt(val.toString());
|
||||
}
|
||||
|
||||
// Getters
|
||||
public Long getId() { return id; }
|
||||
public Client getClient() { return client; }
|
||||
public AddressType getAddressType() { return addressType; }
|
||||
public String getAddressLine1() { return addressLine1; }
|
||||
public String getAddressLine2() { return addressLine2; }
|
||||
public String getPinCode() { return pinCode; }
|
||||
public String getLocality() { return locality; }
|
||||
public String getCity() { return city; }
|
||||
public String getDistrict() { return district; }
|
||||
public String getState() { return state; }
|
||||
public String getCountry() { return country; }
|
||||
public String getLandmark() { return landmark; }
|
||||
public String getProofType() { return proofType; }
|
||||
public String getResidenceType() { return residenceType; }
|
||||
public Boolean getSameAsPermanent() { return sameAsPermanent; }
|
||||
public String getOrgName() { return orgName; }
|
||||
public String getEmploymentType() { return employmentType; }
|
||||
public String getCommMode() { return commMode; }
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* CBS Nexora — India Address REST Controller
|
||||
* Package: org.apache.fineract.portfolio.client.api
|
||||
*
|
||||
* Endpoints:
|
||||
* POST /clients/{clientId}/india-address — Save all 4 address types
|
||||
* GET /clients/{clientId}/india-address — Get all addresses
|
||||
* GET /clients/{clientId}/india-address/{type} — Get specific type
|
||||
* PUT /clients/{clientId}/india-address/{type} — Update specific type
|
||||
* DELETE /clients/{clientId}/india-address — Delete all addresses
|
||||
*/
|
||||
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.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Path("/v1/clients/{clientId}/india-address")
|
||||
@Component
|
||||
@Tag(name = "Client India Address", description = "India-specific multi-type address management")
|
||||
@RequiredArgsConstructor
|
||||
public class ClientAddressIndiaApiResource {
|
||||
|
||||
private final ClientRepositoryWrapper clientRepository;
|
||||
private final ClientAddressIndiaRepository addressRepository;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
|
||||
/**
|
||||
* POST /clients/{clientId}/india-address
|
||||
* Body: { "permanent": {...}, "current": {...}, "office": {...}, "correspondence": {...} }
|
||||
*/
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Transactional
|
||||
@Operation(summary = "Save all address types", description = "Create or update Permanent, Current, Office, and Correspondence addresses")
|
||||
public Response saveAllAddresses(
|
||||
@PathParam("clientId") Long clientId,
|
||||
String jsonBody) {
|
||||
|
||||
final Client client = clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
final JsonObject json = fromJsonHelper.parse(jsonBody).getAsJsonObject();
|
||||
|
||||
List<Long> savedIds = new ArrayList<>();
|
||||
|
||||
for (ClientAddressIndia.AddressType type : ClientAddressIndia.AddressType.values()) {
|
||||
String key = type.name().toLowerCase();
|
||||
if (json.has(key) && json.get(key).isJsonObject()) {
|
||||
JsonObject addrJson = json.getAsJsonObject(key);
|
||||
Map<String, Object> data = toMap(addrJson);
|
||||
|
||||
Optional<ClientAddressIndia> existing = addressRepository.findByClientIdAndAddressType(clientId, type);
|
||||
ClientAddressIndia addr;
|
||||
|
||||
if (existing.isPresent()) {
|
||||
addr = existing.get();
|
||||
addr.update(data);
|
||||
} else {
|
||||
addr = ClientAddressIndia.create(client, type);
|
||||
addr.update(data);
|
||||
}
|
||||
|
||||
addressRepository.saveAndFlush(addr);
|
||||
savedIds.add(addr.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok(Map.of(
|
||||
"clientId", clientId,
|
||||
"savedAddressIds", savedIds
|
||||
)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /clients/{clientId}/india-address
|
||||
*/
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Get all addresses", description = "Retrieve all 4 address types for a client")
|
||||
public Response getAllAddresses(@PathParam("clientId") Long clientId) {
|
||||
clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
List<ClientAddressIndia> addresses = addressRepository.findByClientId(clientId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("clientId", clientId);
|
||||
|
||||
for (ClientAddressIndia addr : addresses) {
|
||||
result.put(addr.getAddressType().name().toLowerCase(), toResponseMap(addr));
|
||||
}
|
||||
|
||||
return Response.ok(result).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /clients/{clientId}/india-address/{type}
|
||||
*/
|
||||
@GET
|
||||
@Path("/{type}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Get specific address type")
|
||||
public Response getAddress(
|
||||
@PathParam("clientId") Long clientId,
|
||||
@PathParam("type") String type) {
|
||||
|
||||
clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
ClientAddressIndia.AddressType addrType = ClientAddressIndia.AddressType.valueOf(type.toUpperCase());
|
||||
Optional<ClientAddressIndia> addr = addressRepository.findByClientIdAndAddressType(clientId, addrType);
|
||||
|
||||
if (addr.isEmpty()) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "No " + type + " address found for client " + clientId))
|
||||
.build();
|
||||
}
|
||||
|
||||
return Response.ok(toResponseMap(addr.get())).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /clients/{clientId}/india-address/{type}
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{type}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Transactional
|
||||
@Operation(summary = "Update specific address type")
|
||||
public Response updateAddress(
|
||||
@PathParam("clientId") Long clientId,
|
||||
@PathParam("type") String type,
|
||||
String jsonBody) {
|
||||
|
||||
final Client client = clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
ClientAddressIndia.AddressType addrType = ClientAddressIndia.AddressType.valueOf(type.toUpperCase());
|
||||
final JsonObject json = fromJsonHelper.parse(jsonBody).getAsJsonObject();
|
||||
final Map<String, Object> data = toMap(json);
|
||||
|
||||
Optional<ClientAddressIndia> existing = addressRepository.findByClientIdAndAddressType(clientId, addrType);
|
||||
ClientAddressIndia addr;
|
||||
|
||||
if (existing.isPresent()) {
|
||||
addr = existing.get();
|
||||
addr.update(data);
|
||||
} else {
|
||||
addr = ClientAddressIndia.create(client, addrType);
|
||||
addr.update(data);
|
||||
}
|
||||
|
||||
addressRepository.saveAndFlush(addr);
|
||||
|
||||
return Response.ok(Map.of(
|
||||
"resourceId", addr.getId(),
|
||||
"clientId", clientId,
|
||||
"addressType", type.toUpperCase()
|
||||
)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /clients/{clientId}/india-address
|
||||
*/
|
||||
@DELETE
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Transactional
|
||||
@Operation(summary = "Delete all addresses")
|
||||
public Response deleteAllAddresses(@PathParam("clientId") Long clientId) {
|
||||
clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
addressRepository.deleteByClientId(clientId);
|
||||
return Response.ok(Map.of("clientId", clientId, "deleted", true)).build();
|
||||
}
|
||||
|
||||
private Map<String, Object> toResponseMap(ClientAddressIndia addr) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("id", addr.getId());
|
||||
map.put("addressType", addr.getAddressType().name());
|
||||
map.put("addressLine1", addr.getAddressLine1());
|
||||
map.put("addressLine2", addr.getAddressLine2());
|
||||
map.put("pinCode", addr.getPinCode());
|
||||
map.put("locality", addr.getLocality());
|
||||
map.put("city", addr.getCity());
|
||||
map.put("district", addr.getDistrict());
|
||||
map.put("state", addr.getState());
|
||||
map.put("country", addr.getCountry());
|
||||
map.put("landmark", addr.getLandmark());
|
||||
map.put("proofType", addr.getProofType());
|
||||
map.put("residenceType", addr.getResidenceType());
|
||||
map.put("sameAsPermanent", addr.getSameAsPermanent());
|
||||
map.put("orgName", addr.getOrgName());
|
||||
map.put("employmentType", addr.getEmploymentType());
|
||||
map.put("commMode", addr.getCommMode());
|
||||
return map;
|
||||
}
|
||||
|
||||
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()) map.put(e.getKey(), prim.getAsInt());
|
||||
else map.put(e.getKey(), prim.getAsString());
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* CBS Nexora — India Address Repository
|
||||
* Package: org.apache.fineract.portfolio.client.domain
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ClientAddressIndiaRepository extends JpaRepository<ClientAddressIndia, Long> {
|
||||
|
||||
List<ClientAddressIndia> findByClientId(Long clientId);
|
||||
|
||||
Optional<ClientAddressIndia> findByClientIdAndAddressType(Long clientId, ClientAddressIndia.AddressType addressType);
|
||||
|
||||
void deleteByClientId(Long clientId);
|
||||
|
||||
boolean existsByClientId(Long clientId);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* CBS Nexora — Entity KYC Domain Object
|
||||
* Package: org.apache.fineract.portfolio.client.domain
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_client_entity_kyc", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "client_id" })
|
||||
})
|
||||
public class ClientEntityKyc {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "client_id", nullable = false)
|
||||
private Client client;
|
||||
|
||||
@Column(name = "constitution_type", length = 50)
|
||||
private String constitutionType;
|
||||
|
||||
// ── MSME ──
|
||||
@Column(name = "is_msme")
|
||||
private Boolean isMsme = false;
|
||||
|
||||
@Column(name = "udyam_number", length = 19)
|
||||
private String udyamNumber;
|
||||
|
||||
@Column(name = "enterprise_name", length = 300)
|
||||
private String enterpriseName;
|
||||
|
||||
@Column(name = "business_sector", length = 50)
|
||||
private String businessSector;
|
||||
|
||||
@Column(name = "msme_turnover", precision = 18, scale = 2)
|
||||
private BigDecimal msmeTurnover;
|
||||
|
||||
@Column(name = "msme_investment", precision = 18, scale = 2)
|
||||
private BigDecimal msmeInvestment;
|
||||
|
||||
@Column(name = "msme_type", length = 20)
|
||||
private String msmeType;
|
||||
|
||||
// ── Entity KYC Document ──
|
||||
@Column(name = "entity_kyc_doc_type", length = 100)
|
||||
private String entityKycDocType;
|
||||
|
||||
@Column(name = "entity_kyc_doc_number", length = 100)
|
||||
private String entityKycDocNumber;
|
||||
|
||||
@Column(name = "entity_kyc_doc_issue_date")
|
||||
private LocalDate entityKycDocIssueDate;
|
||||
|
||||
@Column(name = "entity_kyc_doc_expiry_date")
|
||||
private LocalDate entityKycDocExpiryDate;
|
||||
|
||||
// ── Authorized Signatory ──
|
||||
@Column(name = "signatory_name", length = 200)
|
||||
private String signatoryName;
|
||||
|
||||
@Column(name = "signatory_designation", length = 100)
|
||||
private String signatoryDesignation;
|
||||
|
||||
@Column(name = "signatory_pan", length = 10)
|
||||
private String signatoryPan;
|
||||
|
||||
@Column(name = "signatory_aadhaar_masked", length = 12)
|
||||
private String signatoryAadhaarMasked;
|
||||
|
||||
@Column(name = "signatory_kyc_doc_type", length = 100)
|
||||
private String signatoryKycDocType;
|
||||
|
||||
@Column(name = "signatory_kyc_doc_number", length = 100)
|
||||
private String signatoryKycDocNumber;
|
||||
|
||||
// ── Authorization Document ──
|
||||
@Column(name = "authorization_doc_type", length = 100)
|
||||
private String authorizationDocType;
|
||||
|
||||
@Column(name = "authorization_doc_number", length = 100)
|
||||
private String authorizationDocNumber;
|
||||
|
||||
@Column(name = "authorization_date")
|
||||
private LocalDate authorizationDate;
|
||||
|
||||
// ── Risk ──
|
||||
@Column(name = "entity_risk_category")
|
||||
private Integer entityRiskCategory = 1;
|
||||
|
||||
@Column(name = "entity_remarks", columnDefinition = "TEXT")
|
||||
private String entityRemarks;
|
||||
|
||||
// ── Audit ──
|
||||
@Column(name = "created_date", updatable = false)
|
||||
private LocalDateTime createdDate = LocalDateTime.now();
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private LocalDateTime lastModifiedDate = LocalDateTime.now();
|
||||
|
||||
protected ClientEntityKyc() {}
|
||||
|
||||
public static ClientEntityKyc create(Client client, String constitutionType) {
|
||||
ClientEntityKyc kyc = new ClientEntityKyc();
|
||||
kyc.client = client;
|
||||
kyc.constitutionType = constitutionType;
|
||||
return kyc;
|
||||
}
|
||||
|
||||
public void update(java.util.Map<String, Object> data) {
|
||||
if (data.containsKey("constitutionType")) this.constitutionType = (String) data.get("constitutionType");
|
||||
if (data.containsKey("isMsme")) this.isMsme = (Boolean) data.get("isMsme");
|
||||
if (data.containsKey("udyamNumber")) this.udyamNumber = (String) data.get("udyamNumber");
|
||||
if (data.containsKey("enterpriseName")) this.enterpriseName = (String) data.get("enterpriseName");
|
||||
if (data.containsKey("businessSector")) this.businessSector = (String) data.get("businessSector");
|
||||
if (data.containsKey("msmeTurnover")) this.msmeTurnover = toBigDecimal(data.get("msmeTurnover"));
|
||||
if (data.containsKey("msmeInvestment")) this.msmeInvestment = toBigDecimal(data.get("msmeInvestment"));
|
||||
if (data.containsKey("msmeType")) this.msmeType = (String) data.get("msmeType");
|
||||
if (data.containsKey("entityKycDocType")) this.entityKycDocType = (String) data.get("entityKycDocType");
|
||||
if (data.containsKey("entityKycDocNumber")) this.entityKycDocNumber = (String) data.get("entityKycDocNumber");
|
||||
if (data.containsKey("signatoryName")) this.signatoryName = (String) data.get("signatoryName");
|
||||
if (data.containsKey("signatoryDesignation")) this.signatoryDesignation = (String) data.get("signatoryDesignation");
|
||||
if (data.containsKey("signatoryPan")) this.signatoryPan = (String) data.get("signatoryPan");
|
||||
if (data.containsKey("signatoryAadhaar")) {
|
||||
String raw = (String) data.get("signatoryAadhaar");
|
||||
this.signatoryAadhaarMasked = raw != null && raw.length() == 12 ? "XXXXXXXX" + raw.substring(8) : raw;
|
||||
}
|
||||
if (data.containsKey("signatoryKycDocType")) this.signatoryKycDocType = (String) data.get("signatoryKycDocType");
|
||||
if (data.containsKey("signatoryKycDocNumber")) this.signatoryKycDocNumber = (String) data.get("signatoryKycDocNumber");
|
||||
if (data.containsKey("authorizationDocType")) this.authorizationDocType = (String) data.get("authorizationDocType");
|
||||
if (data.containsKey("authorizationDocNumber")) this.authorizationDocNumber = (String) data.get("authorizationDocNumber");
|
||||
if (data.containsKey("entityRiskCategory")) this.entityRiskCategory = (Integer) data.get("entityRiskCategory");
|
||||
if (data.containsKey("entityRemarks")) this.entityRemarks = (String) data.get("entityRemarks");
|
||||
this.lastModifiedDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
private BigDecimal toBigDecimal(Object val) {
|
||||
if (val == null) return null;
|
||||
if (val instanceof BigDecimal) return (BigDecimal) val;
|
||||
return new BigDecimal(val.toString());
|
||||
}
|
||||
|
||||
// Getters
|
||||
public Long getId() { return id; }
|
||||
public Client getClient() { return client; }
|
||||
public String getConstitutionType() { return constitutionType; }
|
||||
public Boolean getIsMsme() { return isMsme; }
|
||||
public String getUdyamNumber() { return udyamNumber; }
|
||||
public String getEnterpriseName() { return enterpriseName; }
|
||||
public String getBusinessSector() { return businessSector; }
|
||||
public BigDecimal getMsmeTurnover() { return msmeTurnover; }
|
||||
public BigDecimal getMsmeInvestment() { return msmeInvestment; }
|
||||
public String getMsmeType() { return msmeType; }
|
||||
public String getEntityKycDocType() { return entityKycDocType; }
|
||||
public String getEntityKycDocNumber() { return entityKycDocNumber; }
|
||||
public LocalDate getEntityKycDocIssueDate() { return entityKycDocIssueDate; }
|
||||
public LocalDate getEntityKycDocExpiryDate() { return entityKycDocExpiryDate; }
|
||||
public String getSignatoryName() { return signatoryName; }
|
||||
public String getSignatoryDesignation() { return signatoryDesignation; }
|
||||
public String getSignatoryPan() { return signatoryPan; }
|
||||
public String getSignatoryAadhaarMasked() { return signatoryAadhaarMasked; }
|
||||
public String getAuthorizationDocType() { return authorizationDocType; }
|
||||
public String getAuthorizationDocNumber() { return authorizationDocNumber; }
|
||||
public LocalDate getAuthorizationDate() { return authorizationDate; }
|
||||
public Integer getEntityRiskCategory() { return entityRiskCategory; }
|
||||
public String getEntityRemarks() { return entityRemarks; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* CBS Nexora — Entity KYC Repository
|
||||
* Package: org.apache.fineract.portfolio.client.domain
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ClientEntityKycRepository extends JpaRepository<ClientEntityKyc, Long> {
|
||||
|
||||
Optional<ClientEntityKyc> findByClientId(Long clientId);
|
||||
|
||||
boolean existsByClientId(Long clientId);
|
||||
|
||||
void deleteByClientId(Long clientId);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
# CBS Nexora — Backend Schema & API Changes
|
||||
|
||||
## Files in this directory
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `V1__client_schema_extensions.sql` | DB migration — run on Fineract MySQL/MariaDB |
|
||||
| `ClientEntityKyc.java` | JPA Entity for `m_client_entity_kyc` table |
|
||||
| `ClientAddressIndia.java` | JPA Entity for `m_client_address_india` table |
|
||||
| `ClientEntityKycRepository.java` | Spring Data repository |
|
||||
| `ClientAddressIndiaRepository.java` | Spring Data repository |
|
||||
| `ClientEntityKycApiResource.java` | REST controller — `/clients/{id}/entity-kyc` |
|
||||
| `ClientAddressIndiaApiResource.java` | REST controller — `/clients/{id}/india-address` |
|
||||
|
||||
## How to apply
|
||||
|
||||
### Step 1: Run SQL Migration
|
||||
```bash
|
||||
mysql -u root -p fineract_tenantdb < V1__client_schema_extensions.sql
|
||||
```
|
||||
|
||||
### Step 2: Copy Java files to Fineract source
|
||||
```
|
||||
ClientEntityKyc.java → fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/
|
||||
ClientAddressIndia.java → fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/
|
||||
ClientEntityKycRepository.java → fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/
|
||||
ClientAddressIndiaRepository.java → fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/
|
||||
ClientEntityKycApiResource.java → fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/
|
||||
ClientAddressIndiaApiResource.java → fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/
|
||||
```
|
||||
|
||||
### Step 3: Update existing Fineract Client API
|
||||
In `ClientApiResource.java`, the `POST /clients` endpoint needs to accept these additional fields in the request body:
|
||||
- `nationality`, `annualTurnover`, `creditLimit`
|
||||
- `isMinor`, `ageYears`, `ageMonths`, `ageDays`
|
||||
- `guardianName`, `guardianRelation`, `guardianMobile`, `guardianIdNumber`, `guardianAddress`
|
||||
|
||||
In `ClientWritePlatformServiceJpaRepositoryImpl.java`, map these fields to the `m_client` entity during create/update.
|
||||
|
||||
### Step 4: Register new API resources
|
||||
Add to `fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractWebMvcConfigurer.java` or equivalent Spring config.
|
||||
|
||||
### Step 5: Rebuild Fineract
|
||||
```bash
|
||||
./gradlew bootRun
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Entity KYC
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/clients/{clientId}/entity-kyc` | Create/update entity KYC + MSME |
|
||||
| GET | `/clients/{clientId}/entity-kyc` | Retrieve entity KYC |
|
||||
| DELETE | `/clients/{clientId}/entity-kyc` | Delete entity KYC |
|
||||
|
||||
### India Address
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/clients/{clientId}/india-address` | Save all 4 address types |
|
||||
| GET | `/clients/{clientId}/india-address` | Get all addresses |
|
||||
| GET | `/clients/{clientId}/india-address/{type}` | Get specific type (PERMANENT/CURRENT/OFFICE/CORRESPONDENCE) |
|
||||
| PUT | `/clients/{clientId}/india-address/{type}` | Update specific type |
|
||||
| DELETE | `/clients/{clientId}/india-address` | Delete all addresses |
|
||||
|
||||
## Tables Summary
|
||||
|
||||
| Table | New/Modified | Records |
|
||||
|-------|-------------|---------|
|
||||
| `m_client` | Modified — 12 new columns | Guardian, nationality, minor, turnover |
|
||||
| `m_client_non_person` | Modified — 13 new columns | MSME, CIN, enterprise PAN, signatory |
|
||||
| `m_client_kyc` | Modified — 19 new columns | AML, freeze, form60/61, form15G/H, KYC doc |
|
||||
| `m_client_entity_kyc` | **New** | Entity-specific KYC, MSME, signatory, auth doc |
|
||||
| `m_client_address_india` | **New** | 4-type India address with PIN lookup fields |
|
||||
| `m_code_value` | Modified | Added Gender: Transgender, Other |
|
||||
@@ -0,0 +1,149 @@
|
||||
-- ============================================================
|
||||
-- CBS Nexora — Client Schema Extensions
|
||||
-- Fineract DB Migration Script
|
||||
-- Run this AFTER backing up your database
|
||||
-- ============================================================
|
||||
|
||||
-- ─── 1. m_client — New columns ───
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS nationality VARCHAR(20) DEFAULT 'INDIAN';
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS annual_turnover DECIMAL(18,2);
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS credit_limit DECIMAL(18,2);
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS is_minor TINYINT(1) DEFAULT 0;
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS age_years INT;
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS age_months INT;
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS age_days INT;
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS guardian_name VARCHAR(200);
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS guardian_relation VARCHAR(50);
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS guardian_mobile VARCHAR(15);
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS guardian_id_number VARCHAR(50);
|
||||
ALTER TABLE m_client ADD COLUMN IF NOT EXISTS guardian_address TEXT;
|
||||
|
||||
-- ─── 2. m_client_non_person — New columns ───
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS date_of_registration DATE;
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS enterprise_pan VARCHAR(10);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS cin_number VARCHAR(21);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS authorized_signatory VARCHAR(200);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS additional_info VARCHAR(500);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS credit_limit DECIMAL(18,2);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS is_msme TINYINT(1) DEFAULT 0;
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS udyam_number VARCHAR(19);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS enterprise_name VARCHAR(300);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS business_sector VARCHAR(50);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS msme_turnover DECIMAL(18,2);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS msme_investment DECIMAL(18,2);
|
||||
ALTER TABLE m_client_non_person ADD COLUMN IF NOT EXISTS msme_type VARCHAR(20);
|
||||
|
||||
-- ─── 3. m_client_kyc — New columns ───
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS has_pan TINYINT(1) DEFAULT 1;
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS pan_aadhaar_link_status VARCHAR(20);
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS kyc_document_name VARCHAR(100);
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS kyc_document_number VARCHAR(100);
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS kyc_doc_issue_date DATE;
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS kyc_doc_expiry_date DATE;
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS kyc_expiry_date DATE;
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS aml_check_status VARCHAR(20) DEFAULT 'CLEAR';
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS banned_entity_match VARCHAR(20) DEFAULT 'NO';
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS willful_defaulter VARCHAR(5) DEFAULT 'NO';
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS ecgc_sal_flag VARCHAR(5) DEFAULT 'NO';
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS dormant_flag VARCHAR(20) DEFAULT 'ACTIVE';
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS account_status VARCHAR(20) DEFAULT 'ACTIVE';
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS freeze_reason VARCHAR(50);
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS freeze_authority VARCHAR(50);
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS form60_type VARCHAR(10);
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS form60_date DATE;
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS form15_applicable TINYINT(1) DEFAULT 0;
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS form15_type VARCHAR(5);
|
||||
ALTER TABLE m_client_kyc ADD COLUMN IF NOT EXISTS form15_fy VARCHAR(10);
|
||||
|
||||
-- ─── 4. m_client_entity_kyc — New table ───
|
||||
CREATE TABLE IF NOT EXISTS m_client_entity_kyc (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
client_id BIGINT NOT NULL,
|
||||
constitution_type VARCHAR(50),
|
||||
-- MSME
|
||||
is_msme TINYINT(1) DEFAULT 0,
|
||||
udyam_number VARCHAR(19),
|
||||
enterprise_name VARCHAR(300),
|
||||
business_sector VARCHAR(50),
|
||||
msme_turnover DECIMAL(18,2),
|
||||
msme_investment DECIMAL(18,2),
|
||||
msme_type VARCHAR(20),
|
||||
-- Entity KYC Document
|
||||
entity_kyc_doc_type VARCHAR(100),
|
||||
entity_kyc_doc_number VARCHAR(100),
|
||||
entity_kyc_doc_issue_date DATE,
|
||||
entity_kyc_doc_expiry_date DATE,
|
||||
-- Authorized Signatory
|
||||
signatory_name VARCHAR(200),
|
||||
signatory_designation VARCHAR(100),
|
||||
signatory_pan VARCHAR(10),
|
||||
signatory_aadhaar_masked VARCHAR(12),
|
||||
signatory_kyc_doc_type VARCHAR(100),
|
||||
signatory_kyc_doc_number VARCHAR(100),
|
||||
-- Authorization Document
|
||||
authorization_doc_type VARCHAR(100),
|
||||
authorization_doc_number VARCHAR(100),
|
||||
authorization_date DATE,
|
||||
-- Risk
|
||||
entity_risk_category INT DEFAULT 1,
|
||||
entity_remarks TEXT,
|
||||
-- Audit
|
||||
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_modified_date DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (client_id) REFERENCES m_client(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uk_client_entity_kyc (client_id)
|
||||
);
|
||||
|
||||
-- ─── 5. m_client_address_india — New table ───
|
||||
CREATE TABLE IF NOT EXISTS m_client_address_india (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
client_id BIGINT NOT NULL,
|
||||
address_type ENUM('PERMANENT','CURRENT','OFFICE','CORRESPONDENCE') NOT NULL,
|
||||
address_line_1 VARCHAR(500),
|
||||
address_line_2 VARCHAR(500),
|
||||
pin_code VARCHAR(6),
|
||||
locality VARCHAR(200),
|
||||
city VARCHAR(200),
|
||||
district VARCHAR(200),
|
||||
state VARCHAR(200),
|
||||
country VARCHAR(100) DEFAULT 'India',
|
||||
landmark VARCHAR(300),
|
||||
-- Permanent-specific
|
||||
proof_type VARCHAR(100),
|
||||
proof_doc_number VARCHAR(100),
|
||||
-- Current-specific
|
||||
residence_type VARCHAR(50),
|
||||
years_at_address INT,
|
||||
same_as_permanent TINYINT(1) DEFAULT 0,
|
||||
-- Office-specific
|
||||
org_name VARCHAR(300),
|
||||
department VARCHAR(200),
|
||||
office_phone VARCHAR(20),
|
||||
employment_type VARCHAR(100),
|
||||
employee_id VARCHAR(50),
|
||||
years_with_org INT,
|
||||
-- Correspondence-specific
|
||||
comm_mode VARCHAR(50),
|
||||
contact_number VARCHAR(20),
|
||||
-- Audit
|
||||
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_modified_date DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (client_id) REFERENCES m_client(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uk_client_address_type (client_id, address_type)
|
||||
);
|
||||
|
||||
-- ─── 6. Constitution Type code values (if not using API-driven options) ───
|
||||
-- Find Gender code and add missing values
|
||||
SET @gender_code_id = (SELECT id FROM m_code WHERE code_name = 'Gender' LIMIT 1);
|
||||
INSERT IGNORE INTO m_code_value (code_id, code_value, order_position, is_active)
|
||||
VALUES
|
||||
(@gender_code_id, 'Transgender', 3, 1),
|
||||
(@gender_code_id, 'Other', 4, 1);
|
||||
|
||||
-- ─── 7. Indexes for performance ───
|
||||
CREATE INDEX IF NOT EXISTS idx_client_entity_kyc_client ON m_client_entity_kyc(client_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_client_address_india_client ON m_client_address_india(client_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_client_address_india_type ON m_client_address_india(client_id, address_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_client_kyc_expiry ON m_client_kyc(kyc_expiry_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_client_kyc_aml ON m_client_kyc(aml_check_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_client_minor ON m_client(is_minor);
|
||||
Reference in New Issue
Block a user