Fix KYC update on verified records and client address duplication
- Allow KYC updates on VERIFIED records with mandatory remarks for audit trail; reset PAN verification when PAN number changes, moving status to IN_PROGRESS - Fix client address duplicates: deactivate old addresses of same type on create/update, persist isActive flag (missing save), filter reads by is_active 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
87e132f93d
commit
011239f399
+13
@@ -414,6 +414,19 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
this.kycStatus = ClientKycStatus.IN_PROGRESS.getValue();
|
||||
}
|
||||
|
||||
/** Resets PAN verification flags so that re-verification is required (e.g. after DOB or PAN edit). */
|
||||
public void resetPanVerification() {
|
||||
this.panVerified = false;
|
||||
this.panVerifiedOn = null;
|
||||
this.panFullName = null;
|
||||
this.panFirstName = null;
|
||||
this.panLastName = null;
|
||||
this.panCategory = null;
|
||||
this.panStatus = null;
|
||||
this.verifiedOn = null;
|
||||
this.expiryDate = null;
|
||||
}
|
||||
|
||||
/** Called after successful 3rd-party verification of all required documents. */
|
||||
public void markVerified(final String ckycNumber) {
|
||||
this.kycStatus = ClientKycStatus.VERIFIED.getValue();
|
||||
|
||||
+2
-2
@@ -184,7 +184,7 @@ public class AddressReadPlatformServiceImpl implements AddressReadPlatformServic
|
||||
public List<AddressData> retrieveAllClientAddress(final long clientid) {
|
||||
this.context.authenticatedUser();
|
||||
final AddMapper rm = new AddMapper();
|
||||
final String sql = "select " + rm.schema() + " and ca.client_id=?";
|
||||
final String sql = "select " + rm.schema() + " and ca.client_id=? and ca.is_active=true";
|
||||
return this.jdbcTemplate.query(sql, rm, new Object[] { clientid }); // NOSONAR
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ public class AddressReadPlatformServiceImpl implements AddressReadPlatformServic
|
||||
this.context.authenticatedUser();
|
||||
|
||||
final AddMapper rm = new AddMapper();
|
||||
final String sql = "select " + rm.schema() + " and ca.client_id=? and ca.address_type_id=?";
|
||||
final String sql = "select " + rm.schema() + " and ca.client_id=? and ca.address_type_id=? and ca.is_active=true";
|
||||
|
||||
return this.jdbcTemplate.query(sql, rm, new Object[] { clientid, typeid }); // NOSONAR
|
||||
}
|
||||
|
||||
+23
-1
@@ -22,6 +22,7 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
|
||||
@@ -62,12 +63,16 @@ public class AddressWritePlatformServiceImpl implements AddressWritePlatformServ
|
||||
final CodeValue addressTypeIdCodeValue = codeValueRepository.getReferenceById(addressTypeId);
|
||||
final Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
||||
|
||||
// Deactivate existing active addresses of the same type for this client
|
||||
deactivateExistingAddresses(client.getId(), addressTypeIdCodeValue);
|
||||
|
||||
final Address address = createAddress(jsonObject);
|
||||
address.setClient(client);
|
||||
address.setAddressType(addressTypeIdCodeValue);
|
||||
addressRepository.save(address);
|
||||
|
||||
final ClientAddress clientAddress = createClientAddress(client, jsonObject, addressTypeIdCodeValue, address);
|
||||
clientAddress.setIs_active(true);
|
||||
clientAddressRepository.saveAndFlush(clientAddress);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(clientAddress.getId()).build();
|
||||
@@ -103,6 +108,15 @@ public class AddressWritePlatformServiceImpl implements AddressWritePlatformServ
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(clientAddress.getId()).build();
|
||||
}
|
||||
|
||||
private void deactivateExistingAddresses(final Long clientId, final CodeValue addressType) {
|
||||
final List<ClientAddress> existing = clientAddressRepository
|
||||
.findByClientIdAndAddressTypeAndIsActive(clientId, addressType.getId(), true);
|
||||
for (final ClientAddress ca : existing) {
|
||||
ca.setIs_active(false);
|
||||
clientAddressRepository.save(ca);
|
||||
}
|
||||
}
|
||||
|
||||
private ClientAddress createClientAddress(Client client, JsonObject jsonObject, CodeValue addressTypeIdCodeValue, Address address) {
|
||||
boolean clientAddressIsActive = false;
|
||||
if (jsonObject.get("isActive") != null) {
|
||||
@@ -240,14 +254,22 @@ public class AddressWritePlatformServiceImpl implements AddressWritePlatformServ
|
||||
if (is_address_update) {
|
||||
addobj.setUpdatedOn(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||
this.addressRepository.save(addobj);
|
||||
|
||||
}
|
||||
|
||||
final Boolean testActive = command.booleanPrimitiveValueOfParameterNamed("isActive");
|
||||
if (testActive != null) {
|
||||
final boolean active = command.booleanPrimitiveValueOfParameterNamed("isActive");
|
||||
if (active) {
|
||||
// Deactivate other addresses of the same type for this client
|
||||
deactivateExistingAddresses(clientId, clientAddressObj.getAddressType());
|
||||
}
|
||||
clientAddressObj.setIs_active(active);
|
||||
} else {
|
||||
// On update, keep current address active and deactivate others of the same type
|
||||
deactivateExistingAddresses(clientId, clientAddressObj.getAddressType());
|
||||
clientAddressObj.setIs_active(true);
|
||||
}
|
||||
this.clientAddressRepository.saveAndFlush(clientAddressObj);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(clientAddressObj.getId()).build();
|
||||
}
|
||||
|
||||
+7
-2
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
@@ -27,8 +28,12 @@ import org.springframework.data.repository.query.Param;
|
||||
public interface ClientAddressRepository extends JpaRepository<ClientAddress, Long>, JpaSpecificationExecutor<ClientAddress> {
|
||||
|
||||
@Query("SELECT clientAddress FROM ClientAddress clientAddress WHERE clientAddress.client.id = :clientId AND clientAddress.addressType = :addressType AND clientAddress.isActive = :isActive ")
|
||||
ClientAddress findByClientIdAndAddressTypeAndIsActive(@Param("clientId") long clientId, @Param("addressType") CodeValue addressType,
|
||||
@Param("isActive") boolean isActive);
|
||||
ClientAddress findOneByClientIdAndAddressTypeAndIsActive(@Param("clientId") long clientId,
|
||||
@Param("addressType") CodeValue addressType, @Param("isActive") boolean isActive);
|
||||
|
||||
@Query("SELECT ca FROM ClientAddress ca WHERE ca.client.id = :clientId AND ca.addressType.id = :addressTypeId AND ca.isActive = :isActive")
|
||||
List<ClientAddress> findByClientIdAndAddressTypeAndIsActive(@Param("clientId") long clientId,
|
||||
@Param("addressTypeId") long addressTypeId, @Param("isActive") boolean isActive);
|
||||
|
||||
@Query("SELECT clientAddress FROM ClientAddress clientAddress WHERE clientAddress.client.id = :clientId AND clientAddress.address.id = :addressId ")
|
||||
ClientAddress findByClientIdAndAddressId(@Param("clientId") long clientId, @Param("addressId") long addressId);
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ public class ClientAddressRepositoryWrapper {
|
||||
|
||||
public ClientAddress findOneByClientIdAndAddressTypeAndIsActive(final long clientId, final CodeValue addressType,
|
||||
final boolean isActive) {
|
||||
return this.clientAddressRepository.findByClientIdAndAddressTypeAndIsActive(clientId, addressType, isActive);
|
||||
return this.clientAddressRepository.findOneByClientIdAndAddressTypeAndIsActive(clientId, addressType, isActive);
|
||||
}
|
||||
|
||||
public ClientAddress findOneByClientIdAndAddressId(final long clientId, final long addressId) {
|
||||
|
||||
+18
-2
@@ -23,13 +23,17 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
@@ -118,15 +122,27 @@ public class ClientKycWritePlatformServiceJpaRepositoryImpl implements ClientKyc
|
||||
final ClientKyc kyc = kycRepositoryWrapper.findOneWithNotFoundDetection(kycId);
|
||||
|
||||
final ClientKycStatus currentStatus = kyc.getKycStatus();
|
||||
|
||||
// When re-editing a VERIFIED record, remarks are mandatory (audit trail)
|
||||
if (currentStatus.isVerified()) {
|
||||
throw new ClientKycInvalidStateTransitionException("update", currentStatus.name());
|
||||
final String remarks = command.stringValueOfParameterNamed("remarks");
|
||||
if (StringUtils.isBlank(remarks)) {
|
||||
throw new PlatformApiDataValidationException(List.of(ApiParameterError.parameterError(
|
||||
"error.msg.client.kyc.remarks.required.for.reverification",
|
||||
"Remarks are mandatory when updating a verified KYC record.", "remarks")));
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, Object> changes = kyc.update(command);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
// If PAN-related fields changed on a VERIFIED record, reset PAN verification
|
||||
if (currentStatus.isVerified() && changes.containsKey("panNumber")) {
|
||||
kyc.resetPanVerification();
|
||||
changes.put("panVerified", false);
|
||||
}
|
||||
// Move to IN_PROGRESS when documents are updated
|
||||
if (!currentStatus.isInProgress()) {
|
||||
if (!kyc.getKycStatus().isInProgress()) {
|
||||
kyc.markInProgress();
|
||||
changes.put("kycStatus", ClientKycStatus.IN_PROGRESS.getValue());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user