fix(clients): stale signature bug, CKYC fixes, monthly turnover/annual income, senior citizen/PWD classification
- Add m_client.signature_id (single-slot pointer, mirrors image_id) plus ClientSignatureIdAdapter/EntitySignatureIdAdapter so re-uploading a signature updates the existing m_document row in place instead of leaving duplicate rows behind that readers picked inconsistently. - Fix CkycBatchBuilderServiceImpl address-proof classification to read the client identifier's actual m_code (KYCAddressProofType vs KYCIdProofType) instead of a hardcoded string set that disagreed with the CKYC tab and silently dropped Aadhaar-as-address-proof identifiers. - Organize the CKYC ZIP into per-artefact-type folders (PHOTO/SIGNATURE/ID_PROOF/ADDRESS_PROOF). - Fix CkycBatchBuilderServiceImpl.createBatch() using the daily batch sequence number as the ZIP document's parent_entity_id instead of the real m_ckyc_batch.id, which 404'd on download whenever the two diverged (i.e. almost always). - Add monthlyTurnover and annualIncome fields alongside the existing annualTurnover/monthlyIncome. - Add isSeniorCitizen, isPwd, and pwdCategory (proper m_code_value-backed PwdCategory code, not a free-text column) to m_client, following the same pattern as occupation/religion/marital status. - Add Diploma as its own Education Level (with diploma-specific DegreeQualification values) and Graduate as a level distinct from Undergraduate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.documentmanagement.adapter;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Decouples the entity "current signature" pointer from the generic document service, the same
|
||||
* way {@link EntityImageIdAdapter} decouples the "current photo" pointer — lets
|
||||
* DocumentWritePlatformServiceImpl (fineract-document module) give single-slot upsert semantics to
|
||||
* signature uploads without depending on portfolio-module entities directly.
|
||||
*/
|
||||
public interface EntitySignatureIdAdapter {
|
||||
|
||||
/** @param documentName the m_document.name of the document being created/removed */
|
||||
boolean accept(String entityType, String documentName);
|
||||
|
||||
Optional<SignatureIdResult> get(Long entityId);
|
||||
|
||||
Optional<SignatureIdResult> set(Long entityId, Long signatureId);
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
class SignatureIdResult implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String displayName;
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.adapter;
|
||||
|
||||
import static java.util.Objects.nonNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.documentmanagement.adapter.EntitySignatureIdAdapter;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
class ClientSignatureIdAdapter implements EntitySignatureIdAdapter {
|
||||
|
||||
private static final String ENTITY_TYPE = "clients";
|
||||
private static final String DOCUMENT_NAME = "clientSignature";
|
||||
|
||||
private final ClientRepository repository;
|
||||
|
||||
@Override
|
||||
public boolean accept(String entityType, String documentName) {
|
||||
return ENTITY_TYPE.equalsIgnoreCase(entityType) && DOCUMENT_NAME.equalsIgnoreCase(documentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<SignatureIdResult> get(Long entityId) {
|
||||
return repository.findById(entityId).filter(client -> nonNull(client.getSignatureId()))
|
||||
.map(client -> SignatureIdResult.builder().id(client.getSignatureId()).displayName(client.getDisplayName()).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Optional<SignatureIdResult> set(Long entityId, Long signatureId) {
|
||||
final var result = get(entityId);
|
||||
|
||||
if (signatureId == null) {
|
||||
repository.removeSignatureId(entityId);
|
||||
} else {
|
||||
repository.updateByIdAndSignatureId(entityId, signatureId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -63,6 +63,7 @@ public class ClientApiConstants {
|
||||
public static final String CLIENT_CLASSIFICATION = "ClientClassification";
|
||||
|
||||
public static final String OCCUPATION_CATEGORY = "OccupationCategory";
|
||||
public static final String PWD_CATEGORY = "PwdCategory";
|
||||
public static final String OCCUPATION = "Occupation";
|
||||
|
||||
public static final String CLIENT_NON_PERSON_CONSTITUTION = "Constitution";
|
||||
@@ -147,7 +148,13 @@ public class ClientApiConstants {
|
||||
public static final String religionParamName = "religion";
|
||||
public static final String socialCategoryParamName = "socialCategory";
|
||||
public static final String monthlyIncomeParamName = "monthlyIncome";
|
||||
public static final String annualIncomeParamName = "annualIncome";
|
||||
public static final String annualTurnoverParamName = "annualTurnover";
|
||||
public static final String monthlyTurnoverParamName = "monthlyTurnover";
|
||||
public static final String isSeniorCitizenParamName = "isSeniorCitizen";
|
||||
public static final String isPwdParamName = "isPwd";
|
||||
public static final String pwdCategoryIdParamName = "pwdCategoryId";
|
||||
public static final String pwdCategoryParamName = "pwdCategory";
|
||||
public static final String agricultureIncomeParamName = "agricultureIncome";
|
||||
public static final String nonAgricultureIncomeParamName = "nonAgricultureIncome";
|
||||
public static final String ageYearsParamName = "ageYears";
|
||||
@@ -257,8 +264,9 @@ public class ClientApiConstants {
|
||||
staffOptionsParamName, dateOfBirthParamName, genderParamName, clientTypeParamName, clientClassificationParamName,
|
||||
salutationParamName, occupationCategoryParamName, occupationParamName, maritalStatusParamName,
|
||||
educationLevelParamName, degreeQualificationParamName,
|
||||
religionParamName, socialCategoryParamName, monthlyIncomeParamName,
|
||||
annualTurnoverParamName, agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
||||
religionParamName, socialCategoryParamName, monthlyIncomeParamName, annualIncomeParamName,
|
||||
annualTurnoverParamName, monthlyTurnoverParamName, agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
||||
isSeniorCitizenParamName, isPwdParamName, pwdCategoryParamName,
|
||||
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
||||
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianClientDisplayNameParamName, guardianRelationParamName,
|
||||
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
||||
|
||||
@@ -82,7 +82,13 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
||||
private CodeValueData religion;
|
||||
private CodeValueData socialCategory;
|
||||
private BigDecimal monthlyIncome;
|
||||
private BigDecimal annualIncome;
|
||||
private BigDecimal annualTurnover;
|
||||
private BigDecimal monthlyTurnover;
|
||||
private Boolean isSeniorCitizen;
|
||||
private Boolean isPwd;
|
||||
private CodeValueData pwdCategory;
|
||||
private Collection<CodeValueData> pwdCategoryOptions;
|
||||
private BigDecimal agricultureIncome;
|
||||
private BigDecimal nonAgricultureIncome;
|
||||
private Integer ageYears;
|
||||
@@ -406,7 +412,12 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
||||
data.religion = clientData.religion;
|
||||
data.socialCategory = clientData.socialCategory;
|
||||
data.monthlyIncome = clientData.monthlyIncome;
|
||||
data.annualIncome = clientData.annualIncome;
|
||||
data.annualTurnover = clientData.annualTurnover;
|
||||
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||
data.isPwd = clientData.isPwd;
|
||||
data.pwdCategory = clientData.pwdCategory;
|
||||
data.agricultureIncome = clientData.agricultureIncome;
|
||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||
data.ageYears = clientData.ageYears;
|
||||
@@ -462,7 +473,12 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
||||
data.religion = clientData.religion;
|
||||
data.socialCategory = clientData.socialCategory;
|
||||
data.monthlyIncome = clientData.monthlyIncome;
|
||||
data.annualIncome = clientData.annualIncome;
|
||||
data.annualTurnover = clientData.annualTurnover;
|
||||
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||
data.isPwd = clientData.isPwd;
|
||||
data.pwdCategory = clientData.pwdCategory;
|
||||
data.agricultureIncome = clientData.agricultureIncome;
|
||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||
data.ageYears = clientData.ageYears;
|
||||
@@ -509,7 +525,12 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
||||
data.religion = clientData.religion;
|
||||
data.socialCategory = clientData.socialCategory;
|
||||
data.monthlyIncome = clientData.monthlyIncome;
|
||||
data.annualIncome = clientData.annualIncome;
|
||||
data.annualTurnover = clientData.annualTurnover;
|
||||
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||
data.isPwd = clientData.isPwd;
|
||||
data.pwdCategory = clientData.pwdCategory;
|
||||
data.agricultureIncome = clientData.agricultureIncome;
|
||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||
data.ageYears = clientData.ageYears;
|
||||
|
||||
@@ -77,6 +77,9 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
@Column(name = "image_id")
|
||||
private Long imageId;
|
||||
|
||||
@Column(name = "signature_id")
|
||||
private Long signatureId;
|
||||
|
||||
@Column(name = "status_enum", nullable = false)
|
||||
private Integer status;
|
||||
|
||||
@@ -231,9 +234,25 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
@Column(name = "monthly_income")
|
||||
private java.math.BigDecimal monthlyIncome;
|
||||
|
||||
@Column(name = "annual_income")
|
||||
private java.math.BigDecimal annualIncome;
|
||||
|
||||
@Column(name = "annual_turnover")
|
||||
private java.math.BigDecimal annualTurnover;
|
||||
|
||||
@Column(name = "monthly_turnover")
|
||||
private java.math.BigDecimal monthlyTurnover;
|
||||
|
||||
@Column(name = "is_senior_citizen")
|
||||
private Boolean isSeniorCitizen;
|
||||
|
||||
@Column(name = "is_pwd")
|
||||
private Boolean isPwd;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "pwd_category_cv_id")
|
||||
private CodeValue pwdCategory;
|
||||
|
||||
@Column(name = "agriculture_income")
|
||||
private java.math.BigDecimal agricultureIncome;
|
||||
|
||||
@@ -832,6 +851,14 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return this.socialCategory != null ? this.socialCategory.getId() : null;
|
||||
}
|
||||
|
||||
public Long pwdCategoryId() {
|
||||
return this.pwdCategory != null ? this.pwdCategory.getId() : null;
|
||||
}
|
||||
|
||||
public void updatePwdCategory(CodeValue pwdCategory) {
|
||||
this.pwdCategory = pwdCategory;
|
||||
}
|
||||
|
||||
public void updateSocialCategory(CodeValue socialCategory) {
|
||||
this.socialCategory = socialCategory;
|
||||
}
|
||||
|
||||
+8
@@ -55,6 +55,14 @@ public interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecif
|
||||
@Query("UPDATE Client client SET client.imageId = null WHERE client.id = :clientId")
|
||||
void removeImageId(@Param("clientId") Long clientId);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Client client SET client.signatureId = :signatureId WHERE client.id = :clientId")
|
||||
void updateByIdAndSignatureId(@Param("clientId") Long clientId, @Param("signatureId") Long signatureId);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Client client SET client.signatureId = null WHERE client.id = :clientId")
|
||||
void removeSignatureId(@Param("clientId") Long clientId);
|
||||
|
||||
/**
|
||||
* Count clients whose cif_number begins with the given prefix. Used by the CIF number generator
|
||||
* (TTYYYYNNNNNN format) to compute the next sequence within a given (type, year) bucket.
|
||||
|
||||
Reference in New Issue
Block a user