feat(guarantor): enrich guarantor accounts API with loan health and borrower details
- Add borrower name, savings account, guaranteed/released/remaining amounts, loan timeline, and overdue info to GuarantorAccountSummaryData - Fix duplicate rows in GuarantorLoanAccountSummaryDataMapper by joining savings account via guarantor funding details instead of client ID - Fix NPE in GuarantorDomainServiceImpl when loan product guarantee details are null Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1eba2c50ed
commit
fc80869d4b
+34
@@ -21,6 +21,12 @@ package org.apache.fineract.infrastructure.core.serialization;
|
|||||||
import com.google.gson.ExclusionStrategy;
|
import com.google.gson.ExclusionStrategy;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
@@ -112,5 +118,33 @@ public final class GoogleGsonSerializerHelper {
|
|||||||
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
|
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
|
||||||
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
|
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
|
||||||
builder.registerTypeAdapter(ExternalId.class, new ExternalIdAdapter());
|
builder.registerTypeAdapter(ExternalId.class, new ExternalIdAdapter());
|
||||||
|
// Use runtime type when serializing interface-typed fields (e.g. Collection<? extends IGuarantor>)
|
||||||
|
builder.registerTypeAdapterFactory(new TypeAdapterFactory() {
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
|
||||||
|
if (!type.getRawType().isInterface()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final TypeAdapterFactory self = this;
|
||||||
|
return new TypeAdapter<T>() {
|
||||||
|
@Override
|
||||||
|
public void write(final JsonWriter out, final T value) throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TypeAdapter<T> delegate = (TypeAdapter<T>) gson.getDelegateAdapter(self,
|
||||||
|
TypeToken.get((Class<T>) value.getClass()));
|
||||||
|
delegate.write(out, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T read(final JsonReader in) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-1
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.portfolio.accountdetails.data;
|
package org.apache.fineract.portfolio.accountdetails.data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
||||||
|
|
||||||
@@ -44,12 +45,26 @@ public class GuarantorAccountSummaryData {
|
|||||||
private final Boolean isActive;
|
private final Boolean isActive;
|
||||||
private final String relationship;
|
private final String relationship;
|
||||||
private final BigDecimal onHoldAmount;
|
private final BigDecimal onHoldAmount;
|
||||||
|
private final String savingsAccountNo;
|
||||||
|
private final BigDecimal amountReleased;
|
||||||
|
private final String borrowerName;
|
||||||
|
private final BigDecimal totalOverdue;
|
||||||
|
private final LocalDate overdueSinceDate;
|
||||||
|
private final LocalDate submittedOnDate;
|
||||||
|
private final LocalDate approvedOnDate;
|
||||||
|
private final LocalDate disbursedOnDate;
|
||||||
|
private final LocalDate expectedMaturityDate;
|
||||||
|
private final BigDecimal amountRemaining;
|
||||||
|
private final BigDecimal savingsBalance;
|
||||||
|
|
||||||
public GuarantorAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
|
public GuarantorAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
|
||||||
final String loanProductName, final String shortLoanProductName, final LoanStatusEnumData loanStatus,
|
final String loanProductName, final String shortLoanProductName, final LoanStatusEnumData loanStatus,
|
||||||
final EnumOptionData loanType, final Integer loanCycle, final Boolean inArrears, final BigDecimal originalLoan,
|
final EnumOptionData loanType, final Integer loanCycle, final Boolean inArrears, final BigDecimal originalLoan,
|
||||||
final BigDecimal loanBalance, final BigDecimal amountPaid, final Boolean isActive, final String relationship,
|
final BigDecimal loanBalance, final BigDecimal amountPaid, final Boolean isActive, final String relationship,
|
||||||
final BigDecimal onHoldAmount) {
|
final BigDecimal onHoldAmount, final String savingsAccountNo, final BigDecimal amountReleased,
|
||||||
|
final String borrowerName, final BigDecimal totalOverdue, final LocalDate overdueSinceDate,
|
||||||
|
final LocalDate submittedOnDate, final LocalDate approvedOnDate, final LocalDate disbursedOnDate,
|
||||||
|
final LocalDate expectedMaturityDate, final BigDecimal amountRemaining, final BigDecimal savingsBalance) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.accountNo = accountNo;
|
this.accountNo = accountNo;
|
||||||
this.externalId = externalId;
|
this.externalId = externalId;
|
||||||
@@ -66,5 +81,16 @@ public class GuarantorAccountSummaryData {
|
|||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
this.relationship = relationship;
|
this.relationship = relationship;
|
||||||
this.onHoldAmount = onHoldAmount;
|
this.onHoldAmount = onHoldAmount;
|
||||||
|
this.savingsAccountNo = savingsAccountNo;
|
||||||
|
this.amountReleased = amountReleased;
|
||||||
|
this.borrowerName = borrowerName;
|
||||||
|
this.totalOverdue = totalOverdue;
|
||||||
|
this.overdueSinceDate = overdueSinceDate;
|
||||||
|
this.submittedOnDate = submittedOnDate;
|
||||||
|
this.approvedOnDate = approvedOnDate;
|
||||||
|
this.disbursedOnDate = disbursedOnDate;
|
||||||
|
this.expectedMaturityDate = expectedMaturityDate;
|
||||||
|
this.amountRemaining = amountRemaining;
|
||||||
|
this.savingsBalance = savingsBalance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-5
@@ -725,7 +725,8 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
accountsSummary.append(" l.product_id as productId, lp.name as productName, lp.short_name as shortProductName,")
|
accountsSummary.append(" l.product_id as productId, lp.name as productName, lp.short_name as shortProductName,")
|
||||||
.append(" l.loan_status_id as statusId, l.loan_type_enum as loanType,")
|
.append(" l.loan_status_id as statusId, l.loan_type_enum as loanType,")
|
||||||
|
|
||||||
.append("l.principal_disbursed_derived as originalLoan,").append("l.total_outstanding_derived as loanBalance,")
|
.append(" COALESCE(NULLIF(l.principal_disbursed_derived, 0), l.principal_amount) as originalLoan,")
|
||||||
|
.append("l.total_outstanding_derived as loanBalance,")
|
||||||
.append("l.total_repayment_derived as amountPaid,")
|
.append("l.total_repayment_derived as amountPaid,")
|
||||||
|
|
||||||
.append(" l.loan_product_counter as loanCycle,")
|
.append(" l.loan_product_counter as loanCycle,")
|
||||||
@@ -737,12 +738,23 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
.append(" l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate,")
|
.append(" l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate,")
|
||||||
.append(" l.closedon_date as closedOnDate,").append(" la.overdue_since_date_derived as overdueSinceDate,")
|
.append(" l.closedon_date as closedOnDate,").append(" la.overdue_since_date_derived as overdueSinceDate,")
|
||||||
.append(" l.writtenoffon_date as writtenOffOnDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate,")
|
.append(" l.writtenoffon_date as writtenOffOnDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate,")
|
||||||
.append(" g.is_active as isActive,").append(" cv.code_value as relationship,").append(" sa.on_hold_funds_derived")
|
.append(" g.is_active as isActive,").append(" cv.code_value as relationship,")
|
||||||
|
.append(" gfd.amount as on_hold_funds_derived,")
|
||||||
|
.append(" sa.account_no as savingsAccountNo,")
|
||||||
|
.append(" sa.account_balance_derived as savingsBalance,")
|
||||||
|
.append(" gfd.amount_released_derived as amountReleased,")
|
||||||
|
.append(" gfd.amount_remaining_derived as amountRemaining,")
|
||||||
|
.append(" bc.display_name as borrowerName,")
|
||||||
|
.append(" COALESCE(la.total_overdue_derived, 0) as totalOverdue")
|
||||||
.append(" from m_loan l ").append(" join m_guarantor as g on g.loan_id = l.id ")
|
.append(" from m_loan l ").append(" join m_guarantor as g on g.loan_id = l.id ")
|
||||||
.append(" join m_client as c on c.id = g.entity_id ").append(" LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id")
|
.append(" join m_client as c on c.id = g.entity_id ")
|
||||||
|
.append(" join m_client as bc on bc.id = l.client_id ")
|
||||||
|
.append(" LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id")
|
||||||
.append(" left join m_loan_arrears_aging la on la.loan_id = l.id")
|
.append(" left join m_loan_arrears_aging la on la.loan_id = l.id")
|
||||||
.append(" left join m_code_value cv ON cv.id = g.client_reln_cv_id")
|
.append(" left join m_code_value cv ON cv.id = g.client_reln_cv_id")
|
||||||
.append(" left join m_savings_account sa on sa.client_id = c.id");
|
.append(" left join m_guarantor_funding_details gfd on gfd.guarantor_id = g.id")
|
||||||
|
.append(" left join m_portfolio_account_associations paa on paa.id = gfd.account_associations_id")
|
||||||
|
.append(" left join m_savings_account sa on sa.id = paa.linked_savings_account_id");
|
||||||
|
|
||||||
return accountsSummary.toString();
|
return accountsSummary.toString();
|
||||||
}
|
}
|
||||||
@@ -776,8 +788,20 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
final Boolean isActive = rs.getBoolean("isActive");
|
final Boolean isActive = rs.getBoolean("isActive");
|
||||||
|
|
||||||
final String relationship = rs.getString("relationship");
|
final String relationship = rs.getString("relationship");
|
||||||
|
final String savingsAccountNo = rs.getString("savingsAccountNo");
|
||||||
|
final BigDecimal savingsBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "savingsBalance");
|
||||||
|
final BigDecimal amountReleased = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountReleased");
|
||||||
|
final BigDecimal amountRemaining = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountRemaining");
|
||||||
|
final String borrowerName = rs.getString("borrowerName");
|
||||||
|
final BigDecimal totalOverdue = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverdue");
|
||||||
|
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
|
||||||
|
final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
|
||||||
|
final LocalDate disbursedOnDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate");
|
||||||
|
final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate");
|
||||||
return new GuarantorAccountSummaryData(id, accountNo, externalId, productId, loanProductName, shortLoanProductName, loanStatus,
|
return new GuarantorAccountSummaryData(id, accountNo, externalId, productId, loanProductName, shortLoanProductName, loanStatus,
|
||||||
loanType, loanCycle, inArrears, originalLoan, loanBalance, amountPaid, isActive, relationship, onHoldAmount);
|
loanType, loanCycle, inArrears, originalLoan, loanBalance, amountPaid, isActive, relationship, onHoldAmount,
|
||||||
|
savingsAccountNo, amountReleased, borrowerName, totalOverdue, overdueSinceDate,
|
||||||
|
submittedOnDate, approvedOnDate, disbursedOnDate, expectedMaturityDate, amountRemaining, savingsBalance);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -725,7 +725,10 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
|
|||||||
builder.append("c.alternate_dip_verified as alternateDipVerified, ");
|
builder.append("c.alternate_dip_verified as alternateDipVerified, ");
|
||||||
builder.append("c.alternate_dip_lookup_log_id as alternateDipLookupLogId, ");
|
builder.append("c.alternate_dip_lookup_log_id as alternateDipLookupLogId, ");
|
||||||
builder.append("c.alternate_dip_timestamp as alternateDipTimestamp, ");
|
builder.append("c.alternate_dip_timestamp as alternateDipTimestamp, ");
|
||||||
|
builder.append("c.dip_lookup_log_id as dipLookupLogId, ");
|
||||||
|
builder.append("c.dip_timestamp as dipTimestamp, ");
|
||||||
builder.append("c.name_verified as nameVerified, ");
|
builder.append("c.name_verified as nameVerified, ");
|
||||||
|
builder.append("c.screening_log_id as screeningLogId, ");
|
||||||
|
|
||||||
builder.append("c.submittedon_date as submittedOnDate, ");
|
builder.append("c.submittedon_date as submittedOnDate, ");
|
||||||
builder.append("sbu.username as submittedByUsername, ");
|
builder.append("sbu.username as submittedByUsername, ");
|
||||||
|
|||||||
+2
-1
@@ -150,7 +150,8 @@ public class GuarantorData implements IGuarantor {
|
|||||||
public static GuarantorData mergeClientData(final ClientData clientData, final GuarantorData guarantorData) {
|
public static GuarantorData mergeClientData(final ClientData clientData, final GuarantorData guarantorData) {
|
||||||
return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId,
|
return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId,
|
||||||
guarantorData.guarantorType, clientData.getFirstname(), clientData.getLastname(), null, null, null, null, null, null, null,
|
guarantorData.guarantorType, clientData.getFirstname(), clientData.getLastname(), null, null, null, null, null, null, null,
|
||||||
null, null, null, clientData.getOfficeName(), clientData.getActivationDate(), clientData.getExternalId().getValue(),
|
null, null, null, clientData.getOfficeName(), clientData.getActivationDate(),
|
||||||
|
clientData.getExternalId() != null ? clientData.getExternalId().getValue() : null,
|
||||||
guarantorData.status, guarantorData.guarantorFundingDetails, null, guarantorData.allowedClientRelationshipTypes,
|
guarantorData.status, guarantorData.guarantorFundingDetails, null, guarantorData.allowedClientRelationshipTypes,
|
||||||
guarantorData.accountLinkingOptions);
|
guarantorData.accountLinkingOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -111,6 +111,9 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
|||||||
BigDecimal principal = loan.getPrincipal().getAmount();
|
BigDecimal principal = loan.getPrincipal().getAmount();
|
||||||
if (loanProduct.isHoldGuaranteeFunds()) {
|
if (loanProduct.isHoldGuaranteeFunds()) {
|
||||||
LoanProductGuaranteeDetails guaranteeData = loanProduct.getLoanProductGuaranteeDetails();
|
LoanProductGuaranteeDetails guaranteeData = loanProduct.getLoanProductGuaranteeDetails();
|
||||||
|
if (guaranteeData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
|
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
|
||||||
BigDecimal mandatoryAmount = principal.multiply(guaranteeData.getMandatoryGuarantee()).divide(BigDecimal.valueOf(100));
|
BigDecimal mandatoryAmount = principal.multiply(guaranteeData.getMandatoryGuarantee()).divide(BigDecimal.valueOf(100));
|
||||||
BigDecimal minSelfAmount = principal.multiply(guaranteeData.getMinimumGuaranteeFromOwnFunds()).divide(BigDecimal.valueOf(100));
|
BigDecimal minSelfAmount = principal.multiply(guaranteeData.getMinimumGuaranteeFromOwnFunds()).divide(BigDecimal.valueOf(100));
|
||||||
|
|||||||
Reference in New Issue
Block a user