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:
Jaanu423
2026-06-24 13:15:25 +05:30
co-authored by Claude Sonnet 4.6
parent 1eba2c50ed
commit fc80869d4b
7 changed files with 2537 additions and 2446 deletions
@@ -1,116 +1,150 @@
/**
* 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.core.serialization;
import com.google.gson.ExclusionStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.apache.fineract.infrastructure.core.api.DateAdapter;
import org.apache.fineract.infrastructure.core.api.ExternalIdAdapter;
import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
import org.apache.fineract.infrastructure.core.api.LocalDateAdapter;
import org.apache.fineract.infrastructure.core.api.LocalDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter;
import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
import org.springframework.stereotype.Service;
/**
* Helper class for serialization of Java objects into JSON using Google's GSON.
*/
@Service
public final class GoogleGsonSerializerHelper {
public Gson createGsonBuilderForPartialResponseFiltering(final Set<String> responseParameters) {
final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters);
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
registerTypeAdapters(builder);
return builder.create();
}
public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set<String> supportedParameters,
final Set<String> responseParameters) {
final Set<String> parameterNamesToSkip = new HashSet<>();
if (!responseParameters.isEmpty()) {
// strip out all known support parameters from expected response to
// see if unsupported parameters requested for response.
final Set<String> differentParametersDetectedSet = new HashSet<>(responseParameters);
differentParametersDetectedSet.removeAll(supportedParameters);
if (!differentParametersDetectedSet.isEmpty()) {
throw new UnsupportedParameterException(new ArrayList<>(differentParametersDetectedSet));
}
parameterNamesToSkip.addAll(supportedParameters);
parameterNamesToSkip.removeAll(responseParameters);
}
final ExclusionStrategy strategy = new ParameterListExclusionStrategy(parameterNamesToSkip);
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
registerTypeAdapters(builder);
return builder.create();
}
public String serializedJsonFrom(final Gson serializer, final Object[] dataObjects) {
return serializer.toJson(dataObjects);
}
public String serializedJsonFrom(final Gson serializer, final Object singleDataObject) {
return serializer.toJson(singleDataObject);
}
public static Gson createSimpleGson() {
return createGsonBuilder().create();
}
public static GsonBuilder createGsonBuilder() {
final GsonBuilder builder = new GsonBuilder();
registerTypeAdapters(builder);
return builder;
}
public static void registerTypeAdapters(final GsonBuilder builder) {
builder.registerTypeAdapter(java.util.Date.class, new DateAdapter());
builder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter());
// NOTE: was missing, necessary for GSON serialization with JDK 21's restrictive module access
builder.registerTypeAdapter(LocalTime.class, new LocalTimeAdapter());
builder.registerTypeAdapter(ZonedDateTime.class, new JodaDateTimeAdapter());
builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
builder.registerTypeAdapter(ExternalId.class, new ExternalIdAdapter());
}
}
/**
* 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.core.serialization;
import com.google.gson.ExclusionStrategy;
import com.google.gson.Gson;
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.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.apache.fineract.infrastructure.core.api.DateAdapter;
import org.apache.fineract.infrastructure.core.api.ExternalIdAdapter;
import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
import org.apache.fineract.infrastructure.core.api.LocalDateAdapter;
import org.apache.fineract.infrastructure.core.api.LocalDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter;
import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
import org.springframework.stereotype.Service;
/**
* Helper class for serialization of Java objects into JSON using Google's GSON.
*/
@Service
public final class GoogleGsonSerializerHelper {
public Gson createGsonBuilderForPartialResponseFiltering(final Set<String> responseParameters) {
final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters);
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
registerTypeAdapters(builder);
return builder.create();
}
public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set<String> supportedParameters,
final Set<String> responseParameters) {
final Set<String> parameterNamesToSkip = new HashSet<>();
if (!responseParameters.isEmpty()) {
// strip out all known support parameters from expected response to
// see if unsupported parameters requested for response.
final Set<String> differentParametersDetectedSet = new HashSet<>(responseParameters);
differentParametersDetectedSet.removeAll(supportedParameters);
if (!differentParametersDetectedSet.isEmpty()) {
throw new UnsupportedParameterException(new ArrayList<>(differentParametersDetectedSet));
}
parameterNamesToSkip.addAll(supportedParameters);
parameterNamesToSkip.removeAll(responseParameters);
}
final ExclusionStrategy strategy = new ParameterListExclusionStrategy(parameterNamesToSkip);
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
registerTypeAdapters(builder);
return builder.create();
}
public String serializedJsonFrom(final Gson serializer, final Object[] dataObjects) {
return serializer.toJson(dataObjects);
}
public String serializedJsonFrom(final Gson serializer, final Object singleDataObject) {
return serializer.toJson(singleDataObject);
}
public static Gson createSimpleGson() {
return createGsonBuilder().create();
}
public static GsonBuilder createGsonBuilder() {
final GsonBuilder builder = new GsonBuilder();
registerTypeAdapters(builder);
return builder;
}
public static void registerTypeAdapters(final GsonBuilder builder) {
builder.registerTypeAdapter(java.util.Date.class, new DateAdapter());
builder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter());
// NOTE: was missing, necessary for GSON serialization with JDK 21's restrictive module access
builder.registerTypeAdapter(LocalTime.class, new LocalTimeAdapter());
builder.registerTypeAdapter(ZonedDateTime.class, new JodaDateTimeAdapter());
builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
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;
}
};
}
});
}
}