diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java index 1d2b9d56f..48b691278 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java @@ -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 responseParameters) { - final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters); - - final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy); - registerTypeAdapters(builder); - return builder.create(); - } - - public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set supportedParameters, - final Set responseParameters) { - - final Set 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 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 responseParameters) { + final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters); + + final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy); + registerTypeAdapters(builder); + return builder.create(); + } + + public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set supportedParameters, + final Set responseParameters) { + + final Set 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 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) + builder.registerTypeAdapterFactory(new TypeAdapterFactory() { + @Override + @SuppressWarnings("unchecked") + public TypeAdapter create(final Gson gson, final TypeToken type) { + if (!type.getRawType().isInterface()) { + return null; + } + final TypeAdapterFactory self = this; + return new TypeAdapter() { + @Override + public void write(final JsonWriter out, final T value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + TypeAdapter delegate = (TypeAdapter) gson.getDelegateAdapter(self, + TypeToken.get((Class) value.getClass())); + delegate.write(out, value); + } + + @Override + public T read(final JsonReader in) throws IOException { + return null; + } + }; + } + }); + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index f1f026c2a..e1154221a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -1,719 +1,719 @@ -/** - * 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.loanaccount.data; - -import jakarta.persistence.Transient; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; -import org.apache.fineract.infrastructure.codes.data.CodeValueData; -import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; -import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.dataqueries.data.DatatableData; -import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.apache.fineract.organisation.staff.data.StaffData; -import org.apache.fineract.portfolio.account.data.PortfolioAccountData; -import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData; -import org.apache.fineract.portfolio.calendar.data.CalendarData; -import org.apache.fineract.portfolio.charge.data.ChargeData; -import org.apache.fineract.portfolio.client.data.ClientData; -import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData; -import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData; -import org.apache.fineract.portfolio.fund.data.FundData; -import org.apache.fineract.portfolio.group.data.GroupGeneralData; -import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; -import org.apache.fineract.portfolio.loanaccount.guarantor.data.IGuarantor; -import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; -import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; -import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData; -import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; -import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; -import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType; -import org.apache.fineract.portfolio.note.data.NoteData; -import org.apache.fineract.portfolio.rate.data.RateData; - -@Data -@NoArgsConstructor -@Accessors(chain = true) -@SuppressWarnings("ObjectToString") -public class LoanAccountData { - - // basic loan details - - // identity - private Long id; - private String accountNo; - private ExternalId externalId = ExternalId.empty(); - - // status - private LoanStatusEnumData status; - private EnumOptionData subStatus; - - // related to - private Long clientId; - private String clientAccountNo; - private String clientName; - private ExternalId clientExternalId; - private Long clientOfficeId; - private GroupGeneralData group; - private Long loanProductId; - private String loanProductName; - private String loanProductDescription; - // TODO: avoid prefix "is" - private boolean isLoanProductLinkedToFloatingRate; - private Long fundId; - private String fundName; - private Long loanPurposeId; - private String loanPurposeName; - private Long loanOfficerId; - private String loanOfficerName; - private EnumOptionData loanType; - - // terms - private CurrencyData currency; - private BigDecimal principal; - private BigDecimal approvedPrincipal; - private BigDecimal proposedPrincipal; - private BigDecimal netDisbursalAmount; - - private Integer termFrequency; - private EnumOptionData termPeriodFrequencyType; - private Integer numberOfRepayments; - private Integer actualNoTerm; - private Integer repaymentEvery; - private Integer fixedLength; - private EnumOptionData repaymentFrequencyType; - private EnumOptionData repaymentFrequencyNthDayType; - private EnumOptionData repaymentFrequencyDayOfWeekType; - private BigDecimal interestRatePerPeriod; - private EnumOptionData interestRateFrequencyType; - private BigDecimal annualInterestRate; - // TODO: avoid prefix "is" - private boolean isFloatingInterestRate; - private BigDecimal interestRateDifferential; - - // settings - private EnumOptionData amortizationType; - private EnumOptionData interestType; - private EnumOptionData interestCalculationPeriodType; - private Boolean allowPartialPeriodInterestCalculation; - private BigDecimal inArrearsTolerance; - private String transactionProcessingStrategyCode; - private String transactionProcessingStrategyName; - private Integer graceOnPrincipalPayment; - private Integer recurringMoratoriumOnPrincipalPeriods; - private Integer graceOnInterestPayment; - private Integer graceOnInterestCharged; - private Integer graceOnArrearsAgeing; - private LocalDate interestChargedFromDate; - private LocalDate expectedFirstRepaymentOnDate; - private Boolean syncDisbursementWithMeeting; - private Boolean disallowExpectedDisbursements; - - // timeline - private LoanApplicationTimelineData timeline; - - // totals - private LoanSummaryData summary; - - // associations - private LoanScheduleData repaymentSchedule; - private Collection transactions; - private Collection charges; - private Collection collateral; - private Collection guarantors; - private CalendarData meeting; - private Collection notes; - private Collection disbursementDetails; - private LoanScheduleData originalSchedule; - private Collection originators; - // template - private Collection productOptions; - private Collection loanOfficerOptions; - private Collection loanPurposeOptions; - private Collection fundOptions; - private Collection termFrequencyTypeOptions; - private Collection repaymentFrequencyTypeOptions; - private Collection repaymentFrequencyNthDayTypeOptions; - private Collection repaymentFrequencyDaysOfWeekTypeOptions; - - private Collection interestRateFrequencyTypeOptions; - private Collection amortizationTypeOptions; - private Collection interestTypeOptions; - private Collection interestCalculationPeriodTypeOptions; - private Collection transactionProcessingStrategyOptions; - private Collection chargeOptions; - private Collection loanCollateralOptions; - private Collection calendarOptions; - private List loanScheduleTypeOptions; - private List loanScheduleProcessingTypeOptions; - private List daysInYearCustomStrategyOptions; - private List capitalizedIncomeCalculationTypeOptions; - private List capitalizedIncomeStrategyOptions; - private List capitalizedIncomeTypeOptions; - private List buyDownFeeCalculationTypeOptions; - private List buyDownFeeStrategyOptions; - private List buyDownFeeIncomeTypeOptions; - - @Transient - private BigDecimal feeChargesAtDisbursementCharged; - private BigDecimal totalOverpaid; - - // loanCycle - private Integer loanCounter; - private Integer loanProductCounter; - - // linkable account details - private PortfolioAccountData linkedAccount; - private Collection accountLinkingOptions; - - private Boolean multiDisburseLoan; - - private Boolean canDefineInstallmentAmount; - - private BigDecimal fixedEmiAmount; - - private BigDecimal maxOutstandingLoanBalance; - - private Boolean canDisburse; - - private Collection emiAmountVariations; - private Collection loanTermVariations; - private Collection clientActiveLoanOptions; - private Boolean canUseForTopup; - // TODO: avoid prefix "is" - private boolean isTopup; - private boolean fraud; - private Long closureLoanId; - private String closureLoanAccountNo; - private BigDecimal topupAmount; - - private LoanProductData product; - - private Map memberVariations; - - private Boolean inArrears; - // TODO: avoid prefix "is" - private Boolean isNPA; - private Collection overdueCharges; - - private EnumOptionData daysInMonthType; - private EnumOptionData daysInYearType; - private StringEnumOptionData daysInYearCustomStrategy; - // TODO: avoid prefix "is" - private boolean isInterestRecalculationEnabled; - - private LoanInterestRecalculationData interestRecalculationData; - private Boolean createStandingInstructionAtDisbursement; - - // Paid In Advance - private PaidInAdvanceData paidInAdvance; - - private Collection interestRatesPeriods; - - // VariableInstallments - // TODO: avoid prefix "is" - private Boolean isVariableInstallmentsAllowed; - private Integer minimumGap; - private Integer maximumGap; - - private List datatables; - // TODO: avoid prefix "is" - private Boolean isEqualAmortization; - private BigDecimal fixedPrincipalPercentagePerInstallment; - - // Rate - private List rates; - // TODO: avoid prefix "is" - private Boolean isRatesEnabled; - - // import fields - private String dateFormat; - private String locale; - private transient Integer rowIndex; - private LocalDate submittedOnDate; - private Long productId; - private Integer loanTermFrequency; - private EnumOptionData loanTermFrequencyType; - private LocalDate repaymentsStartingFromDate; - private String linkAccountId; - private Long groupId; - private LocalDate expectedDisbursementDate; - - private LocalDate overpaidOnDate; - private CollectionData delinquent; - private DelinquencyRangeData delinquencyRange; - private Boolean enableInstallmentLevelDelinquency; - private LocalDate lastClosedBusinessDate; - private Boolean chargedOff; - private Boolean allowFullTermForTranche; - - private Boolean enableDownPayment; - private BigDecimal disbursedAmountPercentageForDownPayment; - private Boolean enableAutoRepaymentForDownPayment; - private Boolean interestRecognitionOnDisbursementDate; - - private EnumOptionData loanScheduleType; - private EnumOptionData loanScheduleProcessingType; - - private StringEnumOptionData chargeOffBehaviour; - private Boolean enableIncomeCapitalization; - private StringEnumOptionData capitalizedIncomeCalculationType; - private StringEnumOptionData capitalizedIncomeStrategy; - private StringEnumOptionData capitalizedIncomeType; - - private Boolean enableBuyDownFee; - private StringEnumOptionData buyDownFeeCalculationType; - private StringEnumOptionData buyDownFeeStrategy; - private StringEnumOptionData buyDownFeeIncomeType; - private Boolean merchantBuyDownFee; - - public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId, - Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments, - Integer repaymentEvery, EnumOptionData repaidEveryFrequencyEnums, Integer loanTermFrequency, - EnumOptionData loanTermFrequencyTypeEnum, BigDecimal nominalInterestRate, LocalDate expectedDisbursementDate, - EnumOptionData amortizationEnumOption, EnumOptionData interestMethodEnum, EnumOptionData interestCalculationPeriodTypeEnum, - BigDecimal inArrearsTolerance, String transactionProcessingStrategyCode, Integer graceOnPrincipalPayment, - Integer graceOnInterestPayment, Integer graceOnInterestCharged, LocalDate interestChargedFromDate, - LocalDate repaymentsStartingFromDate, Integer rowIndex, ExternalId externalId, Long groupId, Collection charges, - String linkAccountId, String locale, String dateFormat, List loanCollateralManagementData, - Integer fixedLength, StringEnumOptionData daysInYearCustomStrategy) { - - return new LoanAccountData().setLoanType(loanTypeEnumOption).setClientId(clientId).setProductId(productId) - .setLoanOfficerId(loanOfficerId).setSubmittedOnDate(submittedOnDate).setFundId(fundId).setPrincipal(principal) - .setNumberOfRepayments(numberOfRepayments).setRepaymentEvery(repaymentEvery) - .setRepaymentFrequencyType(repaidEveryFrequencyEnums).setLoanTermFrequency(loanTermFrequency) - .setLoanTermFrequencyType(loanTermFrequencyTypeEnum).setInterestRatePerPeriod(nominalInterestRate) - .setExpectedDisbursementDate(expectedDisbursementDate).setAmortizationType(amortizationEnumOption) - .setInterestType(interestMethodEnum).setInterestCalculationPeriodType(interestCalculationPeriodTypeEnum) - .setInArrearsTolerance(inArrearsTolerance).setTransactionProcessingStrategyCode(transactionProcessingStrategyCode) - .setGraceOnPrincipalPayment(graceOnPrincipalPayment).setGraceOnInterestPayment(graceOnInterestPayment) - .setGraceOnInterestCharged(graceOnInterestCharged).setInterestChargedFromDate(interestChargedFromDate) - .setRepaymentsStartingFromDate(repaymentsStartingFromDate).setRowIndex(rowIndex).setExternalId(externalId) - .setGroupId(groupId).setCharges(charges).setLinkAccountId(linkAccountId).setLocale(locale).setDateFormat(dateFormat) - .setCollateral(loanCollateralManagementData).setFixedLength(fixedLength) - .setDaysInYearCustomStrategy(daysInYearCustomStrategy); - } - - public static LoanAccountData importInstanceGroup(EnumOptionData loanTypeEnumOption, Long groupIdforGroupLoan, Long productId, - Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments, - Integer repaidEvery, EnumOptionData repaidEveryFrequencyEnums, Integer loanTermFrequency, - EnumOptionData loanTermFrequencyTypeEnum, BigDecimal nominalInterestRate, LocalDate expectedDisbursementDate, - EnumOptionData amortizationEnumOption, EnumOptionData interestMethodEnum, EnumOptionData interestCalculationPeriodEnum, - BigDecimal arrearsTolerance, String transactionProcessingStrategyCode, Integer graceOnPrincipalPayment, - Integer graceOnInterestPayment, Integer graceOnInterestCharged, LocalDate interestChargedFromDate, - LocalDate repaymentsStartingFromDate, Integer rowIndex, ExternalId externalId, String linkAccountId, String locale, - String dateFormat, Integer fixedLength) { - - return new LoanAccountData().setLoanType(loanTypeEnumOption).setGroupId(groupIdforGroupLoan).setProductId(productId) - .setLoanOfficerId(loanOfficerId).setSubmittedOnDate(submittedOnDate).setFundId(fundId).setPrincipal(principal) - .setNumberOfRepayments(numberOfRepayments).setRepaymentEvery(repaidEvery) - .setRepaymentFrequencyType(repaidEveryFrequencyEnums).setLoanTermFrequency(loanTermFrequency) - .setLoanTermFrequencyType(loanTermFrequencyTypeEnum).setInterestRatePerPeriod(nominalInterestRate) - .setAmortizationType(amortizationEnumOption).setInterestType(interestMethodEnum) - .setExpectedDisbursementDate(expectedDisbursementDate).setInterestCalculationPeriodType(interestCalculationPeriodEnum) - .setInArrearsTolerance(arrearsTolerance).setTransactionProcessingStrategyCode(transactionProcessingStrategyCode) - .setGraceOnPrincipalPayment(graceOnPrincipalPayment).setGraceOnInterestPayment(graceOnInterestPayment) - .setGraceOnInterestCharged(graceOnInterestCharged).setInterestChargedFromDate(interestChargedFromDate) - .setRepaymentsStartingFromDate(repaymentsStartingFromDate).setRowIndex(rowIndex).setExternalId(externalId) - .setLinkAccountId(linkAccountId).setLocale(locale).setDateFormat(dateFormat).setFixedLength(fixedLength); - } - - public LoanAccountData withClientData(final ClientData clientData) { - return this.setClientId(clientData.getId()) // - .setClientAccountNo(clientData.getAccountNo()) // - .setClientName(clientData.getDisplayName()) // - .setClientOfficeId(clientData.getOfficeId()) // - .setClientExternalId(clientData.getExternalId()); // - } - - public LoanAccountData withExpectedDisbursementDate(final LocalDate expectedDisbursementDate) { - if (getTimeline() == null) { - setTimeline(new LoanApplicationTimelineData()); - } - this.getTimeline().setExpectedDisbursementDate(expectedDisbursementDate); - return this.setExpectedDisbursementDate(expectedDisbursementDate); - } - - public LoanAccountData withProductData(final LoanProductData product, final Integer loanCycleNumber) { - - final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType(); - - final Collection charges = new ArrayList(); - for (final ChargeData charge : product.charges()) { - if (!charge.isOverdueInstallmentCharge()) { - charges.add(toLoanChargeData(charge)); - } - } - - BigDecimal principal = null; - BigDecimal proposedPrincipal = null; - BigDecimal interestRatePerPeriod = null; - - Integer numberOfRepayments = null; - if (product.isUseBorrowerCycle() && loanCycleNumber != null && loanCycleNumber > 0) { - Collection principalVariationsForBorrowerCycle = product - .getPrincipalVariationsForBorrowerCycle(); - Collection interestForVariationsForBorrowerCycle = product - .getInterestRateVariationsForBorrowerCycle(); - Collection repaymentVariationsForBorrowerCycle = product - .getNumberOfRepaymentVariationsForBorrowerCycle(); - principal = fetchLoanCycleDefaultValue(principalVariationsForBorrowerCycle, loanCycleNumber); - proposedPrincipal = principal; - interestRatePerPeriod = fetchLoanCycleDefaultValue(interestForVariationsForBorrowerCycle, loanCycleNumber); - BigDecimal numberofRepaymentval = fetchLoanCycleDefaultValue(repaymentVariationsForBorrowerCycle, loanCycleNumber); - if (numberofRepaymentval != null) { - numberOfRepayments = numberofRepaymentval.intValue(); - } - } - - if (principal == null) { - principal = product.getPrincipal(); - proposedPrincipal = principal; - } - - // Add net get net disbursal amount from charges and principal - BigDecimal netDisbursalAmount = principal; - - if (!charges.isEmpty()) { - for (LoanChargeData charge : charges) { - netDisbursalAmount = netDisbursalAmount.subtract(charge.getAmount()); - } - } - - if (interestRatePerPeriod == null) { - interestRatePerPeriod = product.getInterestRatePerPeriod(); - } - if (numberOfRepayments == null) { - numberOfRepayments = product.getNumberOfRepayments(); - } - - return this.setProductId(product.getId()).setLoanProductName(product.getName()).setLoanProductDescription(product.getDescription()) - .setLoanProductLinkedToFloatingRate(product.isLinkedToFloatingInterestRates()).setFundId(product.getFundId()) - .setFundName(product.getFundName()).setCurrency(product.getCurrency()).setProposedPrincipal(proposedPrincipal) - .setPrincipal(principal).setApprovedPrincipal(principal).setNetDisbursalAmount(netDisbursalAmount) - .setInArrearsTolerance(product.getInArrearsTolerance()).setTermFrequency(numberOfRepayments * product.getRepaymentEvery()) - .setTermPeriodFrequencyType(termPeriodFrequencyType).setNumberOfRepayments(numberOfRepayments) - .setRepaymentEvery(product.getRepaymentEvery()).setRepaymentFrequencyType(product.getRepaymentFrequencyType()) - .setTransactionProcessingStrategyCode(product.getTransactionProcessingStrategyCode()) - .setAmortizationType(product.getAmortizationType()).setInterestRatePerPeriod(interestRatePerPeriod) - .setInterestRateFrequencyType(product.getInterestRateFrequencyType()).setAnnualInterestRate(product.getAnnualInterestRate()) - .setInterestType(product.getInterestType()).setFloatingInterestRate(product.isFloatingInterestRateCalculationAllowed()) - .setInterestRateDifferential(product.getDefaultDifferentialLendingRate()) - .setInterestCalculationPeriodType(product.getInterestCalculationPeriodType()) - .setAllowPartialPeriodInterestCalculation(product.isAllowPartialPeriodInterestCalculation()) - .setGraceOnPrincipalPayment(product.getGraceOnPrincipalPayment()) - .setRecurringMoratoriumOnPrincipalPeriods(product.getRecurringMoratoriumOnPrincipalPeriods()) - .setGraceOnInterestPayment(product.getGraceOnInterestPayment()) - .setGraceOnInterestCharged(product.getGraceOnInterestCharged()).setCharges(charges) - .setMultiDisburseLoan(product.getMultiDisburseLoan()).setCanDefineInstallmentAmount(product.isCanDefineInstallmentAmount()) - .setMaxOutstandingLoanBalance(product.getOutstandingLoanBalance()).setProduct(product) - .setGraceOnArrearsAgeing(product.getGraceOnArrearsAgeing()).setOverdueCharges(product.overdueFeeCharges()) - .setDaysInMonthType(product.getDaysInMonthType()).setDaysInYearType(product.getDaysInYearType()) - .setInterestRecalculationEnabled(product.isInterestRecalculationEnabled()) - .setInterestRecalculationData(product.toLoanInterestRecalculationData()) - .setIsVariableInstallmentsAllowed(product.isAllowVariableInstallments()).setMinimumGap(product.getMinimumGap()) - .setMaximumGap(product.getMaximumGap()).setTopup(product.isCanUseForTopup()) - .setIsEqualAmortization(product.isEqualAmortization()) - .setFixedPrincipalPercentagePerInstallment(product.getFixedPrincipalPercentagePerInstallment()) - .setDelinquent(CollectionData.template()).setDisallowExpectedDisbursements(product.getDisallowExpectedDisbursements()) - .setLoanScheduleType(product.getLoanScheduleType()).setLoanScheduleProcessingType(product.getLoanScheduleProcessingType()) - .setInterestRecognitionOnDisbursementDate(product.isInterestRecognitionOnDisbursementDate()) - .setDaysInYearCustomStrategyOptions(product.getDaysInYearCustomStrategyOptions()) - .setDaysInYearCustomStrategy(product.getDaysInYearCustomStrategy()); - } - - /* - * Used to send back loan account data with the basic details coming from query. - */ - public static LoanAccountData basicLoanDetails(final Long id, final String accountNo, final LoanStatusEnumData status, - final ExternalId externalId, final Long clientId, final String clientAccountNo, final String clientName, - final Long clientOfficeId, final ExternalId clientExternalId, final GroupGeneralData group, final EnumOptionData loanType, - final Long loanProductId, final String loanProductName, final String loanProductDescription, - final boolean isLoanProductLinkedToFloatingRate, final Long fundId, final String fundName, final Long loanPurposeId, - final String loanPurposeName, final Long loanOfficerId, final String loanOfficerName, final CurrencyData currencyData, - final BigDecimal proposedPrincipal, final BigDecimal principal, final BigDecimal approvedPrincipal, - final BigDecimal netDisbursalAmount, final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance, - final Integer termFrequency, final EnumOptionData termPeriodFrequencyType, final Integer numberOfRepayments, - final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, EnumOptionData repaymentFrequencyNthDayType, - EnumOptionData repaymentFrequencyDayOfWeekType, final String transactionStrategy, final String transactionStrategyName, - final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType, - final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate, - final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType, - Boolean allowPartialPeriodInterestCalculation, final LocalDate expectedFirstRepaymentOnDate, - final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, - final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, - final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary, - final BigDecimal feeChargesDueAtDisbursementCharged, final Boolean syncDisbursementWithMeeting, final Integer loanCounter, - final Integer loanProductCounter, final Boolean multiDisburseLoan, Boolean canDefineInstallmentAmount, - final BigDecimal fixedEmiAmont, final BigDecimal outstandingLoanBalance, final Boolean inArrears, - final Integer graceOnArrearsAgeing, final Boolean isNPA, final EnumOptionData daysInMonthType, - final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled, - final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement, - final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus, - final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo, - final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment, - final DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud, - LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff, final boolean enableDownPayment, - final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, - final boolean enableInstallmentLevelDelinquency, final EnumOptionData loanScheduleType, - final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, final StringEnumOptionData chargeOffBehaviour, - final boolean isInterestRecognitionOnDisbursementDate, final boolean allowFullTermForTranche, - final StringEnumOptionData daysInYearCustomStrategy, final boolean enableIncomeCapitalization, - final StringEnumOptionData capitalizedIncomeCalculationType, final StringEnumOptionData capitalizedIncomeStrategy, - StringEnumOptionData capitalizedIncomeType, final boolean enableBuyDownFee, - final StringEnumOptionData buyDownFeeCalculationType, final StringEnumOptionData buyDownFeeStrategy, - final StringEnumOptionData buyDownFeeIncomeType, final boolean merchantBuyDownFee) { - - final CollectionData delinquent = CollectionData.template(); - - return new LoanAccountData().setId(id).setAccountNo(accountNo).setStatus(status).setExternalId(externalId).setClientId(clientId) - .setClientAccountNo(clientAccountNo).setClientName(clientName).setClientOfficeId(clientOfficeId) - .setClientExternalId(clientExternalId).setGroup(group).setLoanType(loanType).setLoanProductId(loanProductId) - .setLoanProductName(loanProductName).setLoanProductDescription(loanProductDescription) - .setLoanProductLinkedToFloatingRate(isLoanProductLinkedToFloatingRate).setFundId(fundId).setFundName(fundName) - .setLoanPurposeId(loanPurposeId).setLoanPurposeName(loanPurposeName).setLoanOfficerId(loanOfficerId) - .setLoanOfficerName(loanOfficerName).setCurrency(currencyData).setProposedPrincipal(proposedPrincipal) - .setPrincipal(principal).setApprovedPrincipal(approvedPrincipal).setNetDisbursalAmount(netDisbursalAmount) - .setTotalOverpaid(totalOverpaid).setInArrearsTolerance(inArrearsTolerance).setTermFrequency(termFrequency) - .setTermPeriodFrequencyType(termPeriodFrequencyType).setNumberOfRepayments(numberOfRepayments) - .setRepaymentEvery(repaymentEvery).setRepaymentFrequencyType(repaymentFrequencyType) - .setRepaymentFrequencyNthDayType(repaymentFrequencyNthDayType) - .setRepaymentFrequencyDayOfWeekType(repaymentFrequencyDayOfWeekType) - .setTransactionProcessingStrategyCode(transactionStrategy).setTransactionProcessingStrategyName(transactionStrategyName) - .setAmortizationType(amortizationType).setInterestRatePerPeriod(interestRatePerPeriod) - .setInterestRateFrequencyType(interestRateFrequencyType).setAnnualInterestRate(annualInterestRate) - .setInterestType(interestType).setFloatingInterestRate(isFloatingInterestRate) - .setInterestRateDifferential(interestRateDifferential).setInterestCalculationPeriodType(interestCalculationPeriodType) - .setAllowPartialPeriodInterestCalculation(allowPartialPeriodInterestCalculation) - .setExpectedFirstRepaymentOnDate(expectedFirstRepaymentOnDate).setGraceOnPrincipalPayment(graceOnPrincipalPayment) - .setRecurringMoratoriumOnPrincipalPeriods(recurringMoratoriumOnPrincipalPeriods) - .setGraceOnInterestPayment(graceOnInterestPayment).setGraceOnInterestCharged(graceOnInterestCharged) - .setInterestChargedFromDate(interestChargedFromDate).setTimeline(timeline).setSummary(loanSummary) - .setFeeChargesAtDisbursementCharged(feeChargesDueAtDisbursementCharged) - .setSyncDisbursementWithMeeting(syncDisbursementWithMeeting).setLoanCounter(loanCounter) - .setLoanProductCounter(loanProductCounter).setMultiDisburseLoan(multiDisburseLoan) - .setCanDefineInstallmentAmount(canDefineInstallmentAmount).setFixedEmiAmount(fixedEmiAmont) - .setMaxOutstandingLoanBalance(outstandingLoanBalance).setInArrears(inArrears).setGraceOnArrearsAgeing(graceOnArrearsAgeing) - .setIsNPA(isNPA).setDaysInMonthType(daysInMonthType).setDaysInYearType(daysInYearType) - .setInterestRecalculationEnabled(isInterestRecalculationEnabled).setInterestRecalculationData(interestRecalculationData) - .setCreateStandingInstructionAtDisbursement(createStandingInstructionAtDisbursement) - .setIsVariableInstallmentsAllowed(isVariableInstallmentsAllowed).setMinimumGap(minimumGap).setMaximumGap(maximumGap) - .setSubStatus(subStatus).setCanUseForTopup(canUseForTopup).setTopup(isTopup).setClosureLoanId(closureLoanId) - .setClosureLoanAccountNo(closureLoanAccountNo).setTopupAmount(topupAmount).setIsEqualAmortization(isEqualAmortization) - .setFixedPrincipalPercentagePerInstallment(fixedPrincipalPercentagePerInstallment).setDelinquent(delinquent) - .setDelinquencyRange(delinquencyRange).setDisallowExpectedDisbursements(disallowExpectedDisbursements).setFraud(fraud) - .setLastClosedBusinessDate(lastClosedBusinessDate).setOverpaidOnDate(overpaidOnDate).setChargedOff(chargedOff) - .setEnableDownPayment(enableDownPayment).setDisbursedAmountPercentageForDownPayment(disbursedAmountPercentageForDownPayment) - .setEnableAutoRepaymentForDownPayment(enableAutoRepaymentForDownPayment) - .setEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency).setLoanScheduleType(loanScheduleType) - .setLoanScheduleProcessingType(loanScheduleProcessingType).setFixedLength(fixedLength) - .setChargeOffBehaviour(chargeOffBehaviour).setInterestRecognitionOnDisbursementDate(isInterestRecognitionOnDisbursementDate) - .setAllowFullTermForTranche(allowFullTermForTranche).setDaysInYearCustomStrategy(daysInYearCustomStrategy) - .setEnableIncomeCapitalization(enableIncomeCapitalization) - .setCapitalizedIncomeCalculationType(capitalizedIncomeCalculationType) - .setCapitalizedIncomeStrategy(capitalizedIncomeStrategy).setCapitalizedIncomeType(capitalizedIncomeType) - .setEnableBuyDownFee(enableBuyDownFee).setBuyDownFeeCalculationType(buyDownFeeCalculationType) - .setBuyDownFeeStrategy(buyDownFeeStrategy).setBuyDownFeeIncomeType(buyDownFeeIncomeType) - .setMerchantBuyDownFee(merchantBuyDownFee); - } - - /* - * Used to combine the associations and template data on top of exist loan account data - */ - public LoanAccountData associationsAndTemplate(final LoanScheduleData repaymentSchedule, - final Collection transactions, final Collection charges, - final Collection collateral, final Collection guarantors, - final CalendarData calendarData, final Collection productOptions, - final Collection termFrequencyTypeOptions, final Collection repaymentFrequencyTypeOptions, - final Collection repaymentFrequencyNthDayTypeOptions, - final Collection repaymentFrequencyDayOfWeekTypeOptions, - final Collection transactionProcessingStrategyOptions, - final Collection interestRateFrequencyTypeOptions, final Collection amortizationTypeOptions, - final Collection interestTypeOptions, final Collection interestCalculationPeriodTypeOptions, - final Collection fundOptions, final Collection chargeOptions, final ChargeData chargeTemplate, - final Collection loanOfficerOptions, final Collection loanPurposeOptions, - final Collection loanCollateralOptions, final Collection calendarOptions, - final Collection notes, final Collection accountLinkingOptions, - final PortfolioAccountData linkedAccount, final Collection disbursementDetails, - final Collection emiAmountVariations, final Collection overdueCharges, - final PaidInAdvanceData paidInAdvance, Collection interestRatesPeriods, - final Collection clientActiveLoanOptions, final List rates, final Boolean isRatesEnabled, - final CollectionData delinquent, final List loanScheduleTypeOptions, - final List loanScheduleProcessingTypeOptions, final List loanTermVariations, - final List daysInYearCustomStrategyOptions, - final List capitalizedIncomeCalculationTypeOptions, - final List capitalizedIncomeStrategyOptions, - final List capitalizedIncomeTypeOptions, - final List buyDownFeeCalculationTypeOptions, final List buyDownFeeStrategyOptions, - final List buyDownFeeIncomeTypeOptions) { - - // TODO: why are these variables 'calendarData', 'chargeTemplate' never used (see original private constructor) - - return this.setRepaymentSchedule(repaymentSchedule).setTransactions(transactions).setCharges(charges).setCollateral(collateral) - .setGuarantors(guarantors).setProductOptions(productOptions).setTermFrequencyTypeOptions(termFrequencyTypeOptions) - .setRepaymentFrequencyTypeOptions(repaymentFrequencyTypeOptions) - .setRepaymentFrequencyNthDayTypeOptions(repaymentFrequencyNthDayTypeOptions) - .setRepaymentFrequencyDaysOfWeekTypeOptions(repaymentFrequencyDayOfWeekTypeOptions) - .setTransactionProcessingStrategyOptions(transactionProcessingStrategyOptions) - .setInterestRateFrequencyTypeOptions(interestRateFrequencyTypeOptions).setAmortizationTypeOptions(amortizationTypeOptions) - .setInterestTypeOptions(interestTypeOptions).setInterestCalculationPeriodTypeOptions(interestCalculationPeriodTypeOptions) - .setFundOptions(fundOptions).setChargeOptions(chargeOptions).setLoanOfficerOptions(loanOfficerOptions) - .setLoanPurposeOptions(loanPurposeOptions).setLoanCollateralOptions(loanCollateralOptions) - // .setMeeting(calendarData) - .setCalendarOptions(calendarOptions).setNotes(notes).setAccountLinkingOptions(accountLinkingOptions) - .setLinkedAccount(linkedAccount).setDisbursementDetails(disbursementDetails).setEmiAmountVariations(emiAmountVariations) - .setOverdueCharges(overdueCharges).setPaidInAdvance(paidInAdvance).setInterestRatesPeriods(interestRatesPeriods) - .setClientActiveLoanOptions(clientActiveLoanOptions).setRates(rates).setIsRatesEnabled(isRatesEnabled) - .setDelinquent(delinquent).setLoanScheduleTypeOptions(loanScheduleTypeOptions) - .setLoanScheduleProcessingTypeOptions(loanScheduleProcessingTypeOptions).setLoanTermVariations(loanTermVariations) - .setDaysInYearCustomStrategyOptions(daysInYearCustomStrategyOptions) - .setCapitalizedIncomeCalculationTypeOptions(capitalizedIncomeCalculationTypeOptions) - .setCapitalizedIncomeStrategyOptions(capitalizedIncomeStrategyOptions) - .setCapitalizedIncomeTypeOptions(capitalizedIncomeTypeOptions) - .setBuyDownFeeCalculationTypeOptions(buyDownFeeCalculationTypeOptions) - .setBuyDownFeeStrategyOptions(buyDownFeeStrategyOptions).setBuyDownFeeIncomeTypeOptions(buyDownFeeIncomeTypeOptions); - } - - public LoanAccountData associationsAndTemplate(final Collection productOptions, - final Collection allowedLoanOfficers, final Collection calendarOptions, - final Collection accountLinkingOptions, final Boolean isRatesEnabled) { - return this.setProductOptions(productOptions) // - .setLoanOfficerOptions(allowedLoanOfficers) // - .setCalendarOptions(calendarOptions) // - .setAccountLinkingOptions(accountLinkingOptions) // - .setIsRatesEnabled(isRatesEnabled); // - } - - public LoanAccountData associateMemberVariations(final Map memberLoanCycle) { - final Map memberVariations = new HashMap<>(); - for (Map.Entry mapEntry : memberLoanCycle.entrySet()) { - BigDecimal principal = null; - BigDecimal interestRatePerPeriod = null; - Integer numberOfRepayments = null; - Long clientId = mapEntry.getKey(); - Integer loanCycleNumber = mapEntry.getValue(); - if (product.isUseBorrowerCycle() && loanCycleNumber != null && loanCycleNumber > 0) { - Collection principalVariationsForBorrowerCycle = product - .getPrincipalVariationsForBorrowerCycle(); - Collection interestForVariationsForBorrowerCycle = product - .getInterestRateVariationsForBorrowerCycle(); - Collection repaymentVariationsForBorrowerCycle = product - .getNumberOfRepaymentVariationsForBorrowerCycle(); - principal = fetchLoanCycleDefaultValue(principalVariationsForBorrowerCycle, loanCycleNumber); - interestRatePerPeriod = fetchLoanCycleDefaultValue(interestForVariationsForBorrowerCycle, loanCycleNumber); - BigDecimal numberofRepaymentval = fetchLoanCycleDefaultValue(repaymentVariationsForBorrowerCycle, loanCycleNumber); - if (numberofRepaymentval != null) { - numberOfRepayments = numberofRepaymentval.intValue(); - } - } - if (principal == null) { - principal = product.getPrincipal(); - } - if (interestRatePerPeriod == null) { - interestRatePerPeriod = product.getInterestRatePerPeriod(); - } - if (numberOfRepayments == null) { - numberOfRepayments = product.getNumberOfRepayments(); - } - final Integer termFrequency = numberOfRepayments * product.getRepaymentEvery(); - LoanBorrowerCycleData borrowerCycleData = new LoanBorrowerCycleData(principal, interestRatePerPeriod, numberOfRepayments, - termFrequency); - memberVariations.put(clientId, borrowerCycleData); - } - return this.setMemberVariations(memberVariations); - } - - public LoanAccountData withInterestRecalculationCalendarData(final CalendarData calendarData, - final CalendarData compoundingCalendarData) { - if (interestRecalculationData == null) { - interestRecalculationData = new LoanInterestRecalculationData(); - } - final LoanInterestRecalculationData newInterestRecalculationData = interestRecalculationData.withCalendarData(calendarData, - compoundingCalendarData); - return this.setInterestRecalculationData(newInterestRecalculationData); - } - - public static final Comparator LOAN_ACCOUNT_DATA_COMPARATOR_BY_CLIENT_NAME = (loan1, loan2) -> { - String clientOfLoan1 = loan1.getClientName().toUpperCase(Locale.ENGLISH); - String clientOfLoan2 = loan2.getClientName().toUpperCase(Locale.ENGLISH); - return clientOfLoan1.compareTo(clientOfLoan2); - }; - - private static BigDecimal fetchLoanCycleDefaultValue(Collection borrowerCycleVariationData, - Integer loanCycleNumber) { - BigDecimal defaultValue = null; - Integer cycleNumberSelected = 0; - for (LoanProductBorrowerCycleVariationData data : borrowerCycleVariationData) { - if (isLoanCycleValuesWhenConditionEqual(loanCycleNumber, data) - || isLoanCycleValuesWhenConditionGreterthan(loanCycleNumber, cycleNumberSelected, data)) { - cycleNumberSelected = data.getBorrowerCycleNumber(); - defaultValue = data.getDefaultValue(); - } - } - - return defaultValue; - } - - private static boolean isLoanCycleValuesWhenConditionGreterthan(Integer loanCycleNumber, Integer cycleNumberSelected, - LoanProductBorrowerCycleVariationData data) { - return data.getBorrowerCycleNumber() < loanCycleNumber - && data.getLoanProductValueConditionType().equals(LoanProductValueConditionType.GREATERTHAN) - && cycleNumberSelected < data.getBorrowerCycleNumber(); - } - - private static boolean isLoanCycleValuesWhenConditionEqual(Integer loanCycleNumber, LoanProductBorrowerCycleVariationData data) { - return data.getBorrowerCycleNumber().equals(loanCycleNumber) - && data.getLoanProductValueConditionType().equals(LoanProductValueConditionType.EQUAL); - } - - public Long getInterestRecalculationDetailId() { - if (isInterestRecalculationEnabled) { - return this.interestRecalculationData.getId(); - } - return null; - } - - public boolean isActive() { - return LoanStatus.fromInt(getStatus().getId().intValue()).isActive(); - } - - public static LoanChargeData toLoanChargeData(final ChargeData chargeData) { - - BigDecimal percentage = null; - if (chargeData.getChargeCalculationType().getId() == 2) { - percentage = chargeData.getAmount(); - } - - return LoanChargeData.newLoanChargeDetails(chargeData.getId(), chargeData.getName(), chargeData.getCurrency(), - chargeData.getAmount(), percentage, chargeData.getChargeTimeType(), chargeData.getChargeCalculationType(), - chargeData.isPenalty(), chargeData.getChargePaymentMode(), chargeData.getMinCap(), chargeData.getMaxCap(), - ExternalId.empty()); - } -} +/** + * 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.loanaccount.data; + +import jakarta.persistence.Transient; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.dataqueries.data.DatatableData; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.staff.data.StaffData; +import org.apache.fineract.portfolio.account.data.PortfolioAccountData; +import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData; +import org.apache.fineract.portfolio.calendar.data.CalendarData; +import org.apache.fineract.portfolio.charge.data.ChargeData; +import org.apache.fineract.portfolio.client.data.ClientData; +import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData; +import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData; +import org.apache.fineract.portfolio.fund.data.FundData; +import org.apache.fineract.portfolio.group.data.GroupGeneralData; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.loanaccount.guarantor.data.IGuarantor; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; +import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; +import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType; +import org.apache.fineract.portfolio.note.data.NoteData; +import org.apache.fineract.portfolio.rate.data.RateData; + +@Data +@NoArgsConstructor +@Accessors(chain = true) +@SuppressWarnings("ObjectToString") +public class LoanAccountData { + + // basic loan details + + // identity + private Long id; + private String accountNo; + private ExternalId externalId = ExternalId.empty(); + + // status + private LoanStatusEnumData status; + private EnumOptionData subStatus; + + // related to + private Long clientId; + private String clientAccountNo; + private String clientName; + private ExternalId clientExternalId; + private Long clientOfficeId; + private GroupGeneralData group; + private Long loanProductId; + private String loanProductName; + private String loanProductDescription; + // TODO: avoid prefix "is" + private boolean isLoanProductLinkedToFloatingRate; + private Long fundId; + private String fundName; + private Long loanPurposeId; + private String loanPurposeName; + private Long loanOfficerId; + private String loanOfficerName; + private EnumOptionData loanType; + + // terms + private CurrencyData currency; + private BigDecimal principal; + private BigDecimal approvedPrincipal; + private BigDecimal proposedPrincipal; + private BigDecimal netDisbursalAmount; + + private Integer termFrequency; + private EnumOptionData termPeriodFrequencyType; + private Integer numberOfRepayments; + private Integer actualNoTerm; + private Integer repaymentEvery; + private Integer fixedLength; + private EnumOptionData repaymentFrequencyType; + private EnumOptionData repaymentFrequencyNthDayType; + private EnumOptionData repaymentFrequencyDayOfWeekType; + private BigDecimal interestRatePerPeriod; + private EnumOptionData interestRateFrequencyType; + private BigDecimal annualInterestRate; + // TODO: avoid prefix "is" + private boolean isFloatingInterestRate; + private BigDecimal interestRateDifferential; + + // settings + private EnumOptionData amortizationType; + private EnumOptionData interestType; + private EnumOptionData interestCalculationPeriodType; + private Boolean allowPartialPeriodInterestCalculation; + private BigDecimal inArrearsTolerance; + private String transactionProcessingStrategyCode; + private String transactionProcessingStrategyName; + private Integer graceOnPrincipalPayment; + private Integer recurringMoratoriumOnPrincipalPeriods; + private Integer graceOnInterestPayment; + private Integer graceOnInterestCharged; + private Integer graceOnArrearsAgeing; + private LocalDate interestChargedFromDate; + private LocalDate expectedFirstRepaymentOnDate; + private Boolean syncDisbursementWithMeeting; + private Boolean disallowExpectedDisbursements; + + // timeline + private LoanApplicationTimelineData timeline; + + // totals + private LoanSummaryData summary; + + // associations + private LoanScheduleData repaymentSchedule; + private Collection transactions; + private Collection charges; + private Collection collateral; + private Collection guarantors; + private CalendarData meeting; + private Collection notes; + private Collection disbursementDetails; + private LoanScheduleData originalSchedule; + private Collection originators; + // template + private Collection productOptions; + private Collection loanOfficerOptions; + private Collection loanPurposeOptions; + private Collection fundOptions; + private Collection termFrequencyTypeOptions; + private Collection repaymentFrequencyTypeOptions; + private Collection repaymentFrequencyNthDayTypeOptions; + private Collection repaymentFrequencyDaysOfWeekTypeOptions; + + private Collection interestRateFrequencyTypeOptions; + private Collection amortizationTypeOptions; + private Collection interestTypeOptions; + private Collection interestCalculationPeriodTypeOptions; + private Collection transactionProcessingStrategyOptions; + private Collection chargeOptions; + private Collection loanCollateralOptions; + private Collection calendarOptions; + private List loanScheduleTypeOptions; + private List loanScheduleProcessingTypeOptions; + private List daysInYearCustomStrategyOptions; + private List capitalizedIncomeCalculationTypeOptions; + private List capitalizedIncomeStrategyOptions; + private List capitalizedIncomeTypeOptions; + private List buyDownFeeCalculationTypeOptions; + private List buyDownFeeStrategyOptions; + private List buyDownFeeIncomeTypeOptions; + + @Transient + private BigDecimal feeChargesAtDisbursementCharged; + private BigDecimal totalOverpaid; + + // loanCycle + private Integer loanCounter; + private Integer loanProductCounter; + + // linkable account details + private PortfolioAccountData linkedAccount; + private Collection accountLinkingOptions; + + private Boolean multiDisburseLoan; + + private Boolean canDefineInstallmentAmount; + + private BigDecimal fixedEmiAmount; + + private BigDecimal maxOutstandingLoanBalance; + + private Boolean canDisburse; + + private Collection emiAmountVariations; + private Collection loanTermVariations; + private Collection clientActiveLoanOptions; + private Boolean canUseForTopup; + // TODO: avoid prefix "is" + private boolean isTopup; + private boolean fraud; + private Long closureLoanId; + private String closureLoanAccountNo; + private BigDecimal topupAmount; + + private LoanProductData product; + + private Map memberVariations; + + private Boolean inArrears; + // TODO: avoid prefix "is" + private Boolean isNPA; + private Collection overdueCharges; + + private EnumOptionData daysInMonthType; + private EnumOptionData daysInYearType; + private StringEnumOptionData daysInYearCustomStrategy; + // TODO: avoid prefix "is" + private boolean isInterestRecalculationEnabled; + + private LoanInterestRecalculationData interestRecalculationData; + private Boolean createStandingInstructionAtDisbursement; + + // Paid In Advance + private PaidInAdvanceData paidInAdvance; + + private Collection interestRatesPeriods; + + // VariableInstallments + // TODO: avoid prefix "is" + private Boolean isVariableInstallmentsAllowed; + private Integer minimumGap; + private Integer maximumGap; + + private List datatables; + // TODO: avoid prefix "is" + private Boolean isEqualAmortization; + private BigDecimal fixedPrincipalPercentagePerInstallment; + + // Rate + private List rates; + // TODO: avoid prefix "is" + private Boolean isRatesEnabled; + + // import fields + private String dateFormat; + private String locale; + private transient Integer rowIndex; + private LocalDate submittedOnDate; + private Long productId; + private Integer loanTermFrequency; + private EnumOptionData loanTermFrequencyType; + private LocalDate repaymentsStartingFromDate; + private String linkAccountId; + private Long groupId; + private LocalDate expectedDisbursementDate; + + private LocalDate overpaidOnDate; + private CollectionData delinquent; + private DelinquencyRangeData delinquencyRange; + private Boolean enableInstallmentLevelDelinquency; + private LocalDate lastClosedBusinessDate; + private Boolean chargedOff; + private Boolean allowFullTermForTranche; + + private Boolean enableDownPayment; + private BigDecimal disbursedAmountPercentageForDownPayment; + private Boolean enableAutoRepaymentForDownPayment; + private Boolean interestRecognitionOnDisbursementDate; + + private EnumOptionData loanScheduleType; + private EnumOptionData loanScheduleProcessingType; + + private StringEnumOptionData chargeOffBehaviour; + private Boolean enableIncomeCapitalization; + private StringEnumOptionData capitalizedIncomeCalculationType; + private StringEnumOptionData capitalizedIncomeStrategy; + private StringEnumOptionData capitalizedIncomeType; + + private Boolean enableBuyDownFee; + private StringEnumOptionData buyDownFeeCalculationType; + private StringEnumOptionData buyDownFeeStrategy; + private StringEnumOptionData buyDownFeeIncomeType; + private Boolean merchantBuyDownFee; + + public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId, + Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments, + Integer repaymentEvery, EnumOptionData repaidEveryFrequencyEnums, Integer loanTermFrequency, + EnumOptionData loanTermFrequencyTypeEnum, BigDecimal nominalInterestRate, LocalDate expectedDisbursementDate, + EnumOptionData amortizationEnumOption, EnumOptionData interestMethodEnum, EnumOptionData interestCalculationPeriodTypeEnum, + BigDecimal inArrearsTolerance, String transactionProcessingStrategyCode, Integer graceOnPrincipalPayment, + Integer graceOnInterestPayment, Integer graceOnInterestCharged, LocalDate interestChargedFromDate, + LocalDate repaymentsStartingFromDate, Integer rowIndex, ExternalId externalId, Long groupId, Collection charges, + String linkAccountId, String locale, String dateFormat, List loanCollateralManagementData, + Integer fixedLength, StringEnumOptionData daysInYearCustomStrategy) { + + return new LoanAccountData().setLoanType(loanTypeEnumOption).setClientId(clientId).setProductId(productId) + .setLoanOfficerId(loanOfficerId).setSubmittedOnDate(submittedOnDate).setFundId(fundId).setPrincipal(principal) + .setNumberOfRepayments(numberOfRepayments).setRepaymentEvery(repaymentEvery) + .setRepaymentFrequencyType(repaidEveryFrequencyEnums).setLoanTermFrequency(loanTermFrequency) + .setLoanTermFrequencyType(loanTermFrequencyTypeEnum).setInterestRatePerPeriod(nominalInterestRate) + .setExpectedDisbursementDate(expectedDisbursementDate).setAmortizationType(amortizationEnumOption) + .setInterestType(interestMethodEnum).setInterestCalculationPeriodType(interestCalculationPeriodTypeEnum) + .setInArrearsTolerance(inArrearsTolerance).setTransactionProcessingStrategyCode(transactionProcessingStrategyCode) + .setGraceOnPrincipalPayment(graceOnPrincipalPayment).setGraceOnInterestPayment(graceOnInterestPayment) + .setGraceOnInterestCharged(graceOnInterestCharged).setInterestChargedFromDate(interestChargedFromDate) + .setRepaymentsStartingFromDate(repaymentsStartingFromDate).setRowIndex(rowIndex).setExternalId(externalId) + .setGroupId(groupId).setCharges(charges).setLinkAccountId(linkAccountId).setLocale(locale).setDateFormat(dateFormat) + .setCollateral(loanCollateralManagementData).setFixedLength(fixedLength) + .setDaysInYearCustomStrategy(daysInYearCustomStrategy); + } + + public static LoanAccountData importInstanceGroup(EnumOptionData loanTypeEnumOption, Long groupIdforGroupLoan, Long productId, + Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments, + Integer repaidEvery, EnumOptionData repaidEveryFrequencyEnums, Integer loanTermFrequency, + EnumOptionData loanTermFrequencyTypeEnum, BigDecimal nominalInterestRate, LocalDate expectedDisbursementDate, + EnumOptionData amortizationEnumOption, EnumOptionData interestMethodEnum, EnumOptionData interestCalculationPeriodEnum, + BigDecimal arrearsTolerance, String transactionProcessingStrategyCode, Integer graceOnPrincipalPayment, + Integer graceOnInterestPayment, Integer graceOnInterestCharged, LocalDate interestChargedFromDate, + LocalDate repaymentsStartingFromDate, Integer rowIndex, ExternalId externalId, String linkAccountId, String locale, + String dateFormat, Integer fixedLength) { + + return new LoanAccountData().setLoanType(loanTypeEnumOption).setGroupId(groupIdforGroupLoan).setProductId(productId) + .setLoanOfficerId(loanOfficerId).setSubmittedOnDate(submittedOnDate).setFundId(fundId).setPrincipal(principal) + .setNumberOfRepayments(numberOfRepayments).setRepaymentEvery(repaidEvery) + .setRepaymentFrequencyType(repaidEveryFrequencyEnums).setLoanTermFrequency(loanTermFrequency) + .setLoanTermFrequencyType(loanTermFrequencyTypeEnum).setInterestRatePerPeriod(nominalInterestRate) + .setAmortizationType(amortizationEnumOption).setInterestType(interestMethodEnum) + .setExpectedDisbursementDate(expectedDisbursementDate).setInterestCalculationPeriodType(interestCalculationPeriodEnum) + .setInArrearsTolerance(arrearsTolerance).setTransactionProcessingStrategyCode(transactionProcessingStrategyCode) + .setGraceOnPrincipalPayment(graceOnPrincipalPayment).setGraceOnInterestPayment(graceOnInterestPayment) + .setGraceOnInterestCharged(graceOnInterestCharged).setInterestChargedFromDate(interestChargedFromDate) + .setRepaymentsStartingFromDate(repaymentsStartingFromDate).setRowIndex(rowIndex).setExternalId(externalId) + .setLinkAccountId(linkAccountId).setLocale(locale).setDateFormat(dateFormat).setFixedLength(fixedLength); + } + + public LoanAccountData withClientData(final ClientData clientData) { + return this.setClientId(clientData.getId()) // + .setClientAccountNo(clientData.getAccountNo()) // + .setClientName(clientData.getDisplayName()) // + .setClientOfficeId(clientData.getOfficeId()) // + .setClientExternalId(clientData.getExternalId()); // + } + + public LoanAccountData withExpectedDisbursementDate(final LocalDate expectedDisbursementDate) { + if (getTimeline() == null) { + setTimeline(new LoanApplicationTimelineData()); + } + this.getTimeline().setExpectedDisbursementDate(expectedDisbursementDate); + return this.setExpectedDisbursementDate(expectedDisbursementDate); + } + + public LoanAccountData withProductData(final LoanProductData product, final Integer loanCycleNumber) { + + final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType(); + + final Collection charges = new ArrayList(); + for (final ChargeData charge : product.charges()) { + if (!charge.isOverdueInstallmentCharge()) { + charges.add(toLoanChargeData(charge)); + } + } + + BigDecimal principal = null; + BigDecimal proposedPrincipal = null; + BigDecimal interestRatePerPeriod = null; + + Integer numberOfRepayments = null; + if (product.isUseBorrowerCycle() && loanCycleNumber != null && loanCycleNumber > 0) { + Collection principalVariationsForBorrowerCycle = product + .getPrincipalVariationsForBorrowerCycle(); + Collection interestForVariationsForBorrowerCycle = product + .getInterestRateVariationsForBorrowerCycle(); + Collection repaymentVariationsForBorrowerCycle = product + .getNumberOfRepaymentVariationsForBorrowerCycle(); + principal = fetchLoanCycleDefaultValue(principalVariationsForBorrowerCycle, loanCycleNumber); + proposedPrincipal = principal; + interestRatePerPeriod = fetchLoanCycleDefaultValue(interestForVariationsForBorrowerCycle, loanCycleNumber); + BigDecimal numberofRepaymentval = fetchLoanCycleDefaultValue(repaymentVariationsForBorrowerCycle, loanCycleNumber); + if (numberofRepaymentval != null) { + numberOfRepayments = numberofRepaymentval.intValue(); + } + } + + if (principal == null) { + principal = product.getPrincipal(); + proposedPrincipal = principal; + } + + // Add net get net disbursal amount from charges and principal + BigDecimal netDisbursalAmount = principal; + + if (!charges.isEmpty()) { + for (LoanChargeData charge : charges) { + netDisbursalAmount = netDisbursalAmount.subtract(charge.getAmount()); + } + } + + if (interestRatePerPeriod == null) { + interestRatePerPeriod = product.getInterestRatePerPeriod(); + } + if (numberOfRepayments == null) { + numberOfRepayments = product.getNumberOfRepayments(); + } + + return this.setProductId(product.getId()).setLoanProductName(product.getName()).setLoanProductDescription(product.getDescription()) + .setLoanProductLinkedToFloatingRate(product.isLinkedToFloatingInterestRates()).setFundId(product.getFundId()) + .setFundName(product.getFundName()).setCurrency(product.getCurrency()).setProposedPrincipal(proposedPrincipal) + .setPrincipal(principal).setApprovedPrincipal(principal).setNetDisbursalAmount(netDisbursalAmount) + .setInArrearsTolerance(product.getInArrearsTolerance()).setTermFrequency(numberOfRepayments * product.getRepaymentEvery()) + .setTermPeriodFrequencyType(termPeriodFrequencyType).setNumberOfRepayments(numberOfRepayments) + .setRepaymentEvery(product.getRepaymentEvery()).setRepaymentFrequencyType(product.getRepaymentFrequencyType()) + .setTransactionProcessingStrategyCode(product.getTransactionProcessingStrategyCode()) + .setAmortizationType(product.getAmortizationType()).setInterestRatePerPeriod(interestRatePerPeriod) + .setInterestRateFrequencyType(product.getInterestRateFrequencyType()).setAnnualInterestRate(product.getAnnualInterestRate()) + .setInterestType(product.getInterestType()).setFloatingInterestRate(product.isFloatingInterestRateCalculationAllowed()) + .setInterestRateDifferential(product.getDefaultDifferentialLendingRate()) + .setInterestCalculationPeriodType(product.getInterestCalculationPeriodType()) + .setAllowPartialPeriodInterestCalculation(product.isAllowPartialPeriodInterestCalculation()) + .setGraceOnPrincipalPayment(product.getGraceOnPrincipalPayment()) + .setRecurringMoratoriumOnPrincipalPeriods(product.getRecurringMoratoriumOnPrincipalPeriods()) + .setGraceOnInterestPayment(product.getGraceOnInterestPayment()) + .setGraceOnInterestCharged(product.getGraceOnInterestCharged()).setCharges(charges) + .setMultiDisburseLoan(product.getMultiDisburseLoan()).setCanDefineInstallmentAmount(product.isCanDefineInstallmentAmount()) + .setMaxOutstandingLoanBalance(product.getOutstandingLoanBalance()).setProduct(product) + .setGraceOnArrearsAgeing(product.getGraceOnArrearsAgeing()).setOverdueCharges(product.overdueFeeCharges()) + .setDaysInMonthType(product.getDaysInMonthType()).setDaysInYearType(product.getDaysInYearType()) + .setInterestRecalculationEnabled(product.isInterestRecalculationEnabled()) + .setInterestRecalculationData(product.toLoanInterestRecalculationData()) + .setIsVariableInstallmentsAllowed(product.isAllowVariableInstallments()).setMinimumGap(product.getMinimumGap()) + .setMaximumGap(product.getMaximumGap()).setTopup(product.isCanUseForTopup()) + .setIsEqualAmortization(product.isEqualAmortization()) + .setFixedPrincipalPercentagePerInstallment(product.getFixedPrincipalPercentagePerInstallment()) + .setDelinquent(CollectionData.template()).setDisallowExpectedDisbursements(product.getDisallowExpectedDisbursements()) + .setLoanScheduleType(product.getLoanScheduleType()).setLoanScheduleProcessingType(product.getLoanScheduleProcessingType()) + .setInterestRecognitionOnDisbursementDate(product.isInterestRecognitionOnDisbursementDate()) + .setDaysInYearCustomStrategyOptions(product.getDaysInYearCustomStrategyOptions()) + .setDaysInYearCustomStrategy(product.getDaysInYearCustomStrategy()); + } + + /* + * Used to send back loan account data with the basic details coming from query. + */ + public static LoanAccountData basicLoanDetails(final Long id, final String accountNo, final LoanStatusEnumData status, + final ExternalId externalId, final Long clientId, final String clientAccountNo, final String clientName, + final Long clientOfficeId, final ExternalId clientExternalId, final GroupGeneralData group, final EnumOptionData loanType, + final Long loanProductId, final String loanProductName, final String loanProductDescription, + final boolean isLoanProductLinkedToFloatingRate, final Long fundId, final String fundName, final Long loanPurposeId, + final String loanPurposeName, final Long loanOfficerId, final String loanOfficerName, final CurrencyData currencyData, + final BigDecimal proposedPrincipal, final BigDecimal principal, final BigDecimal approvedPrincipal, + final BigDecimal netDisbursalAmount, final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance, + final Integer termFrequency, final EnumOptionData termPeriodFrequencyType, final Integer numberOfRepayments, + final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, EnumOptionData repaymentFrequencyNthDayType, + EnumOptionData repaymentFrequencyDayOfWeekType, final String transactionStrategy, final String transactionStrategyName, + final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType, + final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate, + final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType, + Boolean allowPartialPeriodInterestCalculation, final LocalDate expectedFirstRepaymentOnDate, + final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, + final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, + final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary, + final BigDecimal feeChargesDueAtDisbursementCharged, final Boolean syncDisbursementWithMeeting, final Integer loanCounter, + final Integer loanProductCounter, final Boolean multiDisburseLoan, Boolean canDefineInstallmentAmount, + final BigDecimal fixedEmiAmont, final BigDecimal outstandingLoanBalance, final Boolean inArrears, + final Integer graceOnArrearsAgeing, final Boolean isNPA, final EnumOptionData daysInMonthType, + final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled, + final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement, + final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus, + final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo, + final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment, + final DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud, + LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff, final boolean enableDownPayment, + final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, + final boolean enableInstallmentLevelDelinquency, final EnumOptionData loanScheduleType, + final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, final StringEnumOptionData chargeOffBehaviour, + final boolean isInterestRecognitionOnDisbursementDate, final boolean allowFullTermForTranche, + final StringEnumOptionData daysInYearCustomStrategy, final boolean enableIncomeCapitalization, + final StringEnumOptionData capitalizedIncomeCalculationType, final StringEnumOptionData capitalizedIncomeStrategy, + StringEnumOptionData capitalizedIncomeType, final boolean enableBuyDownFee, + final StringEnumOptionData buyDownFeeCalculationType, final StringEnumOptionData buyDownFeeStrategy, + final StringEnumOptionData buyDownFeeIncomeType, final boolean merchantBuyDownFee) { + + final CollectionData delinquent = CollectionData.template(); + + return new LoanAccountData().setId(id).setAccountNo(accountNo).setStatus(status).setExternalId(externalId).setClientId(clientId) + .setClientAccountNo(clientAccountNo).setClientName(clientName).setClientOfficeId(clientOfficeId) + .setClientExternalId(clientExternalId).setGroup(group).setLoanType(loanType).setLoanProductId(loanProductId) + .setLoanProductName(loanProductName).setLoanProductDescription(loanProductDescription) + .setLoanProductLinkedToFloatingRate(isLoanProductLinkedToFloatingRate).setFundId(fundId).setFundName(fundName) + .setLoanPurposeId(loanPurposeId).setLoanPurposeName(loanPurposeName).setLoanOfficerId(loanOfficerId) + .setLoanOfficerName(loanOfficerName).setCurrency(currencyData).setProposedPrincipal(proposedPrincipal) + .setPrincipal(principal).setApprovedPrincipal(approvedPrincipal).setNetDisbursalAmount(netDisbursalAmount) + .setTotalOverpaid(totalOverpaid).setInArrearsTolerance(inArrearsTolerance).setTermFrequency(termFrequency) + .setTermPeriodFrequencyType(termPeriodFrequencyType).setNumberOfRepayments(numberOfRepayments) + .setRepaymentEvery(repaymentEvery).setRepaymentFrequencyType(repaymentFrequencyType) + .setRepaymentFrequencyNthDayType(repaymentFrequencyNthDayType) + .setRepaymentFrequencyDayOfWeekType(repaymentFrequencyDayOfWeekType) + .setTransactionProcessingStrategyCode(transactionStrategy).setTransactionProcessingStrategyName(transactionStrategyName) + .setAmortizationType(amortizationType).setInterestRatePerPeriod(interestRatePerPeriod) + .setInterestRateFrequencyType(interestRateFrequencyType).setAnnualInterestRate(annualInterestRate) + .setInterestType(interestType).setFloatingInterestRate(isFloatingInterestRate) + .setInterestRateDifferential(interestRateDifferential).setInterestCalculationPeriodType(interestCalculationPeriodType) + .setAllowPartialPeriodInterestCalculation(allowPartialPeriodInterestCalculation) + .setExpectedFirstRepaymentOnDate(expectedFirstRepaymentOnDate).setGraceOnPrincipalPayment(graceOnPrincipalPayment) + .setRecurringMoratoriumOnPrincipalPeriods(recurringMoratoriumOnPrincipalPeriods) + .setGraceOnInterestPayment(graceOnInterestPayment).setGraceOnInterestCharged(graceOnInterestCharged) + .setInterestChargedFromDate(interestChargedFromDate).setTimeline(timeline).setSummary(loanSummary) + .setFeeChargesAtDisbursementCharged(feeChargesDueAtDisbursementCharged) + .setSyncDisbursementWithMeeting(syncDisbursementWithMeeting).setLoanCounter(loanCounter) + .setLoanProductCounter(loanProductCounter).setMultiDisburseLoan(multiDisburseLoan) + .setCanDefineInstallmentAmount(canDefineInstallmentAmount).setFixedEmiAmount(fixedEmiAmont) + .setMaxOutstandingLoanBalance(outstandingLoanBalance).setInArrears(inArrears).setGraceOnArrearsAgeing(graceOnArrearsAgeing) + .setIsNPA(isNPA).setDaysInMonthType(daysInMonthType).setDaysInYearType(daysInYearType) + .setInterestRecalculationEnabled(isInterestRecalculationEnabled).setInterestRecalculationData(interestRecalculationData) + .setCreateStandingInstructionAtDisbursement(createStandingInstructionAtDisbursement) + .setIsVariableInstallmentsAllowed(isVariableInstallmentsAllowed).setMinimumGap(minimumGap).setMaximumGap(maximumGap) + .setSubStatus(subStatus).setCanUseForTopup(canUseForTopup).setTopup(isTopup).setClosureLoanId(closureLoanId) + .setClosureLoanAccountNo(closureLoanAccountNo).setTopupAmount(topupAmount).setIsEqualAmortization(isEqualAmortization) + .setFixedPrincipalPercentagePerInstallment(fixedPrincipalPercentagePerInstallment).setDelinquent(delinquent) + .setDelinquencyRange(delinquencyRange).setDisallowExpectedDisbursements(disallowExpectedDisbursements).setFraud(fraud) + .setLastClosedBusinessDate(lastClosedBusinessDate).setOverpaidOnDate(overpaidOnDate).setChargedOff(chargedOff) + .setEnableDownPayment(enableDownPayment).setDisbursedAmountPercentageForDownPayment(disbursedAmountPercentageForDownPayment) + .setEnableAutoRepaymentForDownPayment(enableAutoRepaymentForDownPayment) + .setEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency).setLoanScheduleType(loanScheduleType) + .setLoanScheduleProcessingType(loanScheduleProcessingType).setFixedLength(fixedLength) + .setChargeOffBehaviour(chargeOffBehaviour).setInterestRecognitionOnDisbursementDate(isInterestRecognitionOnDisbursementDate) + .setAllowFullTermForTranche(allowFullTermForTranche).setDaysInYearCustomStrategy(daysInYearCustomStrategy) + .setEnableIncomeCapitalization(enableIncomeCapitalization) + .setCapitalizedIncomeCalculationType(capitalizedIncomeCalculationType) + .setCapitalizedIncomeStrategy(capitalizedIncomeStrategy).setCapitalizedIncomeType(capitalizedIncomeType) + .setEnableBuyDownFee(enableBuyDownFee).setBuyDownFeeCalculationType(buyDownFeeCalculationType) + .setBuyDownFeeStrategy(buyDownFeeStrategy).setBuyDownFeeIncomeType(buyDownFeeIncomeType) + .setMerchantBuyDownFee(merchantBuyDownFee); + } + + /* + * Used to combine the associations and template data on top of exist loan account data + */ + public LoanAccountData associationsAndTemplate(final LoanScheduleData repaymentSchedule, + final Collection transactions, final Collection charges, + final Collection collateral, final Collection guarantors, + final CalendarData calendarData, final Collection productOptions, + final Collection termFrequencyTypeOptions, final Collection repaymentFrequencyTypeOptions, + final Collection repaymentFrequencyNthDayTypeOptions, + final Collection repaymentFrequencyDayOfWeekTypeOptions, + final Collection transactionProcessingStrategyOptions, + final Collection interestRateFrequencyTypeOptions, final Collection amortizationTypeOptions, + final Collection interestTypeOptions, final Collection interestCalculationPeriodTypeOptions, + final Collection fundOptions, final Collection chargeOptions, final ChargeData chargeTemplate, + final Collection loanOfficerOptions, final Collection loanPurposeOptions, + final Collection loanCollateralOptions, final Collection calendarOptions, + final Collection notes, final Collection accountLinkingOptions, + final PortfolioAccountData linkedAccount, final Collection disbursementDetails, + final Collection emiAmountVariations, final Collection overdueCharges, + final PaidInAdvanceData paidInAdvance, Collection interestRatesPeriods, + final Collection clientActiveLoanOptions, final List rates, final Boolean isRatesEnabled, + final CollectionData delinquent, final List loanScheduleTypeOptions, + final List loanScheduleProcessingTypeOptions, final List loanTermVariations, + final List daysInYearCustomStrategyOptions, + final List capitalizedIncomeCalculationTypeOptions, + final List capitalizedIncomeStrategyOptions, + final List capitalizedIncomeTypeOptions, + final List buyDownFeeCalculationTypeOptions, final List buyDownFeeStrategyOptions, + final List buyDownFeeIncomeTypeOptions) { + + // TODO: why are these variables 'calendarData', 'chargeTemplate' never used (see original private constructor) + + return this.setRepaymentSchedule(repaymentSchedule).setTransactions(transactions).setCharges(charges).setCollateral(collateral) + .setGuarantors(guarantors).setProductOptions(productOptions).setTermFrequencyTypeOptions(termFrequencyTypeOptions) + .setRepaymentFrequencyTypeOptions(repaymentFrequencyTypeOptions) + .setRepaymentFrequencyNthDayTypeOptions(repaymentFrequencyNthDayTypeOptions) + .setRepaymentFrequencyDaysOfWeekTypeOptions(repaymentFrequencyDayOfWeekTypeOptions) + .setTransactionProcessingStrategyOptions(transactionProcessingStrategyOptions) + .setInterestRateFrequencyTypeOptions(interestRateFrequencyTypeOptions).setAmortizationTypeOptions(amortizationTypeOptions) + .setInterestTypeOptions(interestTypeOptions).setInterestCalculationPeriodTypeOptions(interestCalculationPeriodTypeOptions) + .setFundOptions(fundOptions).setChargeOptions(chargeOptions).setLoanOfficerOptions(loanOfficerOptions) + .setLoanPurposeOptions(loanPurposeOptions).setLoanCollateralOptions(loanCollateralOptions) + // .setMeeting(calendarData) + .setCalendarOptions(calendarOptions).setNotes(notes).setAccountLinkingOptions(accountLinkingOptions) + .setLinkedAccount(linkedAccount).setDisbursementDetails(disbursementDetails).setEmiAmountVariations(emiAmountVariations) + .setOverdueCharges(overdueCharges).setPaidInAdvance(paidInAdvance).setInterestRatesPeriods(interestRatesPeriods) + .setClientActiveLoanOptions(clientActiveLoanOptions).setRates(rates).setIsRatesEnabled(isRatesEnabled) + .setDelinquent(delinquent).setLoanScheduleTypeOptions(loanScheduleTypeOptions) + .setLoanScheduleProcessingTypeOptions(loanScheduleProcessingTypeOptions).setLoanTermVariations(loanTermVariations) + .setDaysInYearCustomStrategyOptions(daysInYearCustomStrategyOptions) + .setCapitalizedIncomeCalculationTypeOptions(capitalizedIncomeCalculationTypeOptions) + .setCapitalizedIncomeStrategyOptions(capitalizedIncomeStrategyOptions) + .setCapitalizedIncomeTypeOptions(capitalizedIncomeTypeOptions) + .setBuyDownFeeCalculationTypeOptions(buyDownFeeCalculationTypeOptions) + .setBuyDownFeeStrategyOptions(buyDownFeeStrategyOptions).setBuyDownFeeIncomeTypeOptions(buyDownFeeIncomeTypeOptions); + } + + public LoanAccountData associationsAndTemplate(final Collection productOptions, + final Collection allowedLoanOfficers, final Collection calendarOptions, + final Collection accountLinkingOptions, final Boolean isRatesEnabled) { + return this.setProductOptions(productOptions) // + .setLoanOfficerOptions(allowedLoanOfficers) // + .setCalendarOptions(calendarOptions) // + .setAccountLinkingOptions(accountLinkingOptions) // + .setIsRatesEnabled(isRatesEnabled); // + } + + public LoanAccountData associateMemberVariations(final Map memberLoanCycle) { + final Map memberVariations = new HashMap<>(); + for (Map.Entry mapEntry : memberLoanCycle.entrySet()) { + BigDecimal principal = null; + BigDecimal interestRatePerPeriod = null; + Integer numberOfRepayments = null; + Long clientId = mapEntry.getKey(); + Integer loanCycleNumber = mapEntry.getValue(); + if (product.isUseBorrowerCycle() && loanCycleNumber != null && loanCycleNumber > 0) { + Collection principalVariationsForBorrowerCycle = product + .getPrincipalVariationsForBorrowerCycle(); + Collection interestForVariationsForBorrowerCycle = product + .getInterestRateVariationsForBorrowerCycle(); + Collection repaymentVariationsForBorrowerCycle = product + .getNumberOfRepaymentVariationsForBorrowerCycle(); + principal = fetchLoanCycleDefaultValue(principalVariationsForBorrowerCycle, loanCycleNumber); + interestRatePerPeriod = fetchLoanCycleDefaultValue(interestForVariationsForBorrowerCycle, loanCycleNumber); + BigDecimal numberofRepaymentval = fetchLoanCycleDefaultValue(repaymentVariationsForBorrowerCycle, loanCycleNumber); + if (numberofRepaymentval != null) { + numberOfRepayments = numberofRepaymentval.intValue(); + } + } + if (principal == null) { + principal = product.getPrincipal(); + } + if (interestRatePerPeriod == null) { + interestRatePerPeriod = product.getInterestRatePerPeriod(); + } + if (numberOfRepayments == null) { + numberOfRepayments = product.getNumberOfRepayments(); + } + final Integer termFrequency = numberOfRepayments * product.getRepaymentEvery(); + LoanBorrowerCycleData borrowerCycleData = new LoanBorrowerCycleData(principal, interestRatePerPeriod, numberOfRepayments, + termFrequency); + memberVariations.put(clientId, borrowerCycleData); + } + return this.setMemberVariations(memberVariations); + } + + public LoanAccountData withInterestRecalculationCalendarData(final CalendarData calendarData, + final CalendarData compoundingCalendarData) { + if (interestRecalculationData == null) { + interestRecalculationData = new LoanInterestRecalculationData(); + } + final LoanInterestRecalculationData newInterestRecalculationData = interestRecalculationData.withCalendarData(calendarData, + compoundingCalendarData); + return this.setInterestRecalculationData(newInterestRecalculationData); + } + + public static final Comparator LOAN_ACCOUNT_DATA_COMPARATOR_BY_CLIENT_NAME = (loan1, loan2) -> { + String clientOfLoan1 = loan1.getClientName().toUpperCase(Locale.ENGLISH); + String clientOfLoan2 = loan2.getClientName().toUpperCase(Locale.ENGLISH); + return clientOfLoan1.compareTo(clientOfLoan2); + }; + + private static BigDecimal fetchLoanCycleDefaultValue(Collection borrowerCycleVariationData, + Integer loanCycleNumber) { + BigDecimal defaultValue = null; + Integer cycleNumberSelected = 0; + for (LoanProductBorrowerCycleVariationData data : borrowerCycleVariationData) { + if (isLoanCycleValuesWhenConditionEqual(loanCycleNumber, data) + || isLoanCycleValuesWhenConditionGreterthan(loanCycleNumber, cycleNumberSelected, data)) { + cycleNumberSelected = data.getBorrowerCycleNumber(); + defaultValue = data.getDefaultValue(); + } + } + + return defaultValue; + } + + private static boolean isLoanCycleValuesWhenConditionGreterthan(Integer loanCycleNumber, Integer cycleNumberSelected, + LoanProductBorrowerCycleVariationData data) { + return data.getBorrowerCycleNumber() < loanCycleNumber + && data.getLoanProductValueConditionType().equals(LoanProductValueConditionType.GREATERTHAN) + && cycleNumberSelected < data.getBorrowerCycleNumber(); + } + + private static boolean isLoanCycleValuesWhenConditionEqual(Integer loanCycleNumber, LoanProductBorrowerCycleVariationData data) { + return data.getBorrowerCycleNumber().equals(loanCycleNumber) + && data.getLoanProductValueConditionType().equals(LoanProductValueConditionType.EQUAL); + } + + public Long getInterestRecalculationDetailId() { + if (isInterestRecalculationEnabled) { + return this.interestRecalculationData.getId(); + } + return null; + } + + public boolean isActive() { + return LoanStatus.fromInt(getStatus().getId().intValue()).isActive(); + } + + public static LoanChargeData toLoanChargeData(final ChargeData chargeData) { + + BigDecimal percentage = null; + if (chargeData.getChargeCalculationType().getId() == 2) { + percentage = chargeData.getAmount(); + } + + return LoanChargeData.newLoanChargeDetails(chargeData.getId(), chargeData.getName(), chargeData.getCurrency(), + chargeData.getAmount(), percentage, chargeData.getChargeTimeType(), chargeData.getChargeCalculationType(), + chargeData.isPenalty(), chargeData.getChargePaymentMode(), chargeData.getMinCap(), chargeData.getMaxCap(), + ExternalId.empty()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/GuarantorAccountSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/GuarantorAccountSummaryData.java index 914b9b263..77abfe786 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/GuarantorAccountSummaryData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/data/GuarantorAccountSummaryData.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.accountdetails.data; import java.math.BigDecimal; +import java.time.LocalDate; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData; @@ -44,12 +45,26 @@ public class GuarantorAccountSummaryData { private final Boolean isActive; private final String relationship; 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, final String loanProductName, final String shortLoanProductName, final LoanStatusEnumData loanStatus, 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 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.accountNo = accountNo; this.externalId = externalId; @@ -66,5 +81,16 @@ public class GuarantorAccountSummaryData { this.isActive = isActive; this.relationship = relationship; 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; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java index c0df9484c..70d5fb0d9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java @@ -1,785 +1,809 @@ -/** - * 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.accountdetails.service; - -import java.math.BigDecimal; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; -import java.util.Collection; -import java.util.List; -import lombok.AllArgsConstructor; -import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.infrastructure.core.domain.JdbcSupport; -import org.apache.fineract.infrastructure.security.utils.ColumnValidator; -import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData; -import org.apache.fineract.portfolio.accountdetails.data.GuarantorAccountSummaryData; -import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData; -import org.apache.fineract.portfolio.accountdetails.data.SavingsAccountSummaryData; -import org.apache.fineract.portfolio.accountdetails.data.ShareAccountSummaryData; -import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; -import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; -import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData; -import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData; -import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; -import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData; -import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; -import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountApplicationTimelineData; -import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountStatusEnumData; -import org.apache.fineract.portfolio.shareaccounts.service.SharesEnumerations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; - -@Service -@AllArgsConstructor -public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements AccountDetailsReadPlatformService { - - private final JdbcTemplate jdbcTemplate; - private final ClientReadPlatformService clientReadPlatformService; - private final GroupReadPlatformService groupReadPlatformService; - private final ColumnValidator columnValidator; - - @Override - public AccountSummaryCollectionData retrieveClientAccountDetails(final Long clientId) { - // Check if client exists - this.clientReadPlatformService.retrieveOne(clientId); - final String loanwhereClause = " where l.client_id = ?"; - final String glimLoanClause = " where l.client_id = ? and l.loan_type_enum=4"; - - // note to self: investigate the effect of have loans stored as another - // type especially given that we want to maintain how the system - // currently handles loans of exisiting types. - final String savingswhereClause = " where sa.client_id = ? order by sa.status_enum ASC, sa.account_no ASC"; - - final String guarantorWhereClause = " where g.entity_id = ? and g.is_active = true order by l.account_no ASC"; - - final List glimAccounts = retrieveLoanAccountDetails(glimLoanClause, new Object[] { clientId }); - final List loanAccounts = retrieveLoanAccountDetails(loanwhereClause, new Object[] { clientId }); - final List savingsAccounts = retrieveAccountDetails(savingswhereClause, new Object[] { clientId }); - final List shareAccounts = retrieveShareAccountDetails(clientId); - final List guarantorloanAccounts = retrieveGuarantorLoanAccountDetails(guarantorWhereClause, - new Object[] { clientId }); - final List currentAccounts = retrieveCurrentAccountDetails(clientId); - return new AccountSummaryCollectionData(loanAccounts, glimAccounts, savingsAccounts, shareAccounts, guarantorloanAccounts, currentAccounts); - } - - @Override - public AccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId) { - // Check if group exists - this.groupReadPlatformService.retrieveOne(groupId); - final String loanWhereClauseForGroup = " where l.group_id = ? and l.client_id is null"; - final String loanWhereClauseForGroupAndLoanType = " where l.group_id = ? and l.loan_type_enum=4"; - final String loanWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null"; - final String savingswhereClauseForGroup = " where sa.group_id = ? and sa.client_id is null order by sa.status_enum ASC, sa.account_no ASC"; - final String savingswhereClauseForMembers = " where sa.group_id = ? and sa.client_id is not null order by sa.status_enum ASC, sa.account_no ASC"; - - final String guarantorWhereClauseForGroup = " where l.group_id = ? and l.client_id is null and g.is_active = true order by l.account_no ASC"; - final String guarantorWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null and g.is_active = true order by l.account_no ASC"; - final List glimAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroupAndLoanType, - new Object[] { groupId }); - - final List groupLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroup, - new Object[] { groupId }); - final List groupSavingsAccounts = retrieveAccountDetails(savingswhereClauseForGroup, - new Object[] { groupId }); - final List groupGuarantorloanAccounts = retrieveGuarantorLoanAccountDetails( - guarantorWhereClauseForGroup, new Object[] { groupId }); - final List memberLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForMembers, - new Object[] { groupId }); - final List memberSavingsAccounts = retrieveAccountDetails(savingswhereClauseForMembers, - new Object[] { groupId }); - - final List memberGuarantorloanAccounts = retrieveGuarantorLoanAccountDetails( - guarantorWhereClauseForMembers, new Object[] { groupId }); - return new AccountSummaryCollectionData(groupLoanAccounts, glimAccounts, groupSavingsAccounts, groupGuarantorloanAccounts, - memberLoanAccounts, memberSavingsAccounts, memberGuarantorloanAccounts); - - } - - @Override - public AccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId, final Long gsimId) { - // Check if group exists - this.groupReadPlatformService.retrieveOne(groupId); - final String loanWhereClauseForGroup = " where l.group_id = ? and l.client_id is null"; - final String loanWhereClauseForGroupAndLoanType = " where l.group_id = ? and l.loan_type_enum=4"; - final String loanWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null"; - final String savingswhereClauseForGroup = " where sa.group_id = ? and sa.gsim_id = ? sa.client_id is null order by sa.status_enum ASC, sa.account_no ASC"; - final String savingswhereClauseForMembers = " where sa.group_id = ? and sa.client_id is not null order by sa.status_enum ASC, sa.account_no ASC"; - - final List glimAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroupAndLoanType, - new Object[] { groupId }); - final List groupLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroup, - new Object[] { groupId }); - final List gsimSavingsAccounts = retrieveAccountDetails(savingswhereClauseForGroup, - new Object[] { groupId, gsimId }); - final List memberLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForMembers, - new Object[] { groupId }); - final List memberSavingsAccounts = retrieveAccountDetails(savingswhereClauseForMembers, - new Object[] { groupId }); - return new AccountSummaryCollectionData(groupLoanAccounts, glimAccounts, gsimSavingsAccounts, null, memberLoanAccounts, - memberSavingsAccounts, null); - } - - @Override - public Collection retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId) { - // Check if client exists - this.clientReadPlatformService.retrieveOne(clientId); - final String loanWhereClause = " where l.client_id = ? and l.loan_officer_id = ?"; - return retrieveLoanAccountDetails(loanWhereClause, new Object[] { clientId, loanOfficerId }); - } - - @Override - public Collection retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId, final Long loanOfficerId) { - // Check if group exists - this.groupReadPlatformService.retrieveOne(groupId); - final String loanWhereClause = " where l.group_id = ? and l.client_id is null and l.loan_officer_id = ?"; - return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId, loanOfficerId }); - } - - @Override - public Collection retrieveClientActiveLoanAccountSummary(final Long clientId) { - final String loanWhereClause = " where l.client_id = ? and l.loan_status_id = 300 "; - return retrieveLoanAccountDetails(loanWhereClause, new Object[] { clientId }); - } - - @Override - public List retrieveLoanAccountDetailsByGroupIdAndGlimAccountNumber(final Long groupId, - final String glimAccount) { - final LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper(); - final String loanWhereClauseForGroupAndLoanType = " where l.group_id =? and glim.account_number=? and l.loan_type_enum=4"; - final String sql = "select " + rm.loanAccountSummarySchema() + loanWhereClauseForGroupAndLoanType; - return this.jdbcTemplate.query(sql, rm, new Object[] { groupId, glimAccount }); // NOSONAR - } - - @Override - public Collection retrieveGroupActiveLoanAccountSummary(final Long groupId) { - final String loanWhereClause = " where l.group_id = ? and l.loan_status_id = 300 and l.client_id is null"; - return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId }); - } - - private List retrieveCurrentAccountDetails(final Long clientId) { - final CurrentAccountSummaryDataMapper mapper = new CurrentAccountSummaryDataMapper(); - final String sql = "select " + mapper.schema() + " where ca.client_id = ? order by ca.status_enum ASC, ca.account_no ASC"; - return this.jdbcTemplate.query(sql, mapper, new Object[] { clientId }); // NOSONAR - } - - private List retrieveLoanAccountDetails(final String loanwhereClause, final Object[] inputs) { - final LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper(); - final String sql = "select " + rm.loanAccountSummarySchema() + loanwhereClause; - this.columnValidator.validateSqlInjection(rm.loanAccountSummarySchema(), loanwhereClause); - return this.jdbcTemplate.query(sql, rm, inputs); // NOSONAR - } - - /** - * @param entityId - * @return - */ - private List retrieveAccountDetails(final String savingswhereClause, final Object[] inputs) { - final SavingsAccountSummaryDataMapper savingsAccountSummaryDataMapper = new SavingsAccountSummaryDataMapper(); - final String savingsSql = "select " + savingsAccountSummaryDataMapper.schema() + savingswhereClause; - this.columnValidator.validateSqlInjection(savingsAccountSummaryDataMapper.schema(), savingswhereClause); - return this.jdbcTemplate.query(savingsSql, savingsAccountSummaryDataMapper, inputs); // NOSONAR - } - - private List retrieveShareAccountDetails(final Long clientId) { - final ShareAccountSummaryDataMapper mapper = new ShareAccountSummaryDataMapper(); - final String query = "select " + mapper.schema() + " where sa.client_id = ?"; - return this.jdbcTemplate.query(query, mapper, new Object[] { clientId }); // NOSONAR - } - - private List retrieveGuarantorLoanAccountDetails(final String loanwhereClause, final Object[] inputs) { - final GuarantorLoanAccountSummaryDataMapper rm = new GuarantorLoanAccountSummaryDataMapper(); - final String sql = "select " + rm.guarantorLoanAccountSummarySchema() + loanwhereClause; - return this.jdbcTemplate.query(sql, rm, inputs); // NOSONAR - } - - private static final class ShareAccountSummaryDataMapper implements RowMapper { - - private final String schema; - - ShareAccountSummaryDataMapper() { - final StringBuilder buff = new StringBuilder() - .append("sa.id as id, sa.external_id as externalId, sa.status_enum as statusEnum, ") - .append("sa.account_no as accountNo, sa.total_approved_shares as approvedShares, sa.total_pending_shares as pendingShares, ") - .append("sa.savings_account_id as savingsAccountNo, sa.minimum_active_period_frequency as minimumactivePeriod,") - .append("sa.minimum_active_period_frequency_enum as minimumactivePeriodEnum,") - .append("sa.lockin_period_frequency as lockinPeriod, sa.lockin_period_frequency_enum as lockinPeriodEnum, ") - .append("sa.submitted_date as submittedDate, sbu.username as submittedByUsername, ") - .append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname, ") - .append("sa.rejected_date as rejectedDate, rbu.username as rejectedByUsername, ") - .append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname, ") - .append("sa.approved_date as approvedDate, abu.username as approvedByUsername, ") - .append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname, ") - .append("sa.activated_date as activatedDate, avbu.username as activatedByUsername, ") - .append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname, ") - .append("sa.closed_date as closedDate, cbu.username as closedByUsername, ") - .append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, ") - .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ") - .append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ") - .append("curr.display_symbol as currencyDisplaySymbol, sa.product_id as productId, p.name as productName, p.short_name as shortProductName ") - .append("from m_share_account sa ").append("join m_share_product as p on p.id = sa.product_id ") - .append("join m_currency curr on curr.code = sa.currency_code ") - .append("left join m_appuser sbu on sbu.id = sa.submitted_userid ") - .append("left join m_appuser rbu on rbu.id = sa.rejected_userid ") - .append("left join m_appuser abu on abu.id = sa.approved_userid ") - .append("left join m_appuser avbu on avbu.id = sa.activated_userid ") - .append("left join m_appuser cbu on cbu.id = sa.closed_userid "); - schema = buff.toString(); - } - - @Override - public ShareAccountSummaryData mapRow(ResultSet rs, int rowNum) throws SQLException { - - final Long id = JdbcSupport.getLong(rs, "id"); - final String accountNo = rs.getString("accountNo"); - final Long approvedShares = JdbcSupport.getLong(rs, "approvedShares"); - final Long pendingShares = JdbcSupport.getLong(rs, "pendingShares"); - final String externalId = rs.getString("externalId"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - final String productName = rs.getString("productName"); - final String shortProductName = rs.getString("shortProductName"); - final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); - final ShareAccountStatusEnumData status = SharesEnumerations.status(statusId); - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedDate"); - final String submittedByUsername = rs.getString("submittedByUsername"); - final String submittedByFirstname = rs.getString("submittedByFirstname"); - final String submittedByLastname = rs.getString("submittedByLastname"); - - final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedDate"); - final String rejectedByUsername = rs.getString("rejectedByUsername"); - final String rejectedByFirstname = rs.getString("rejectedByFirstname"); - final String rejectedByLastname = rs.getString("rejectedByLastname"); - - final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedDate"); - final String approvedByUsername = rs.getString("approvedByUsername"); - final String approvedByFirstname = rs.getString("approvedByFirstname"); - final String approvedByLastname = rs.getString("approvedByLastname"); - - final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedDate"); - final String activatedByUsername = rs.getString("activatedByUsername"); - final String activatedByFirstname = rs.getString("activatedByFirstname"); - final String activatedByLastname = rs.getString("activatedByLastname"); - - final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedDate"); - final String closedByUsername = rs.getString("closedByUsername"); - final String closedByFirstname = rs.getString("closedByFirstname"); - final String closedByLastname = rs.getString("closedByLastname"); - - final ShareAccountApplicationTimelineData timeline = new ShareAccountApplicationTimelineData(submittedOnDate, - submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, - rejectedByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, - activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, - closedByLastname); - - return new ShareAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, - approvedShares, pendingShares, timeline); - } - - public String schema() { - return this.schema; - } - } - - private static final class SavingsAccountSummaryDataMapper implements RowMapper { - - final String schemaSql; - - SavingsAccountSummaryDataMapper() { - final StringBuilder accountsSummary = new StringBuilder(); - accountsSummary.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, sa.status_enum as statusEnum, "); - accountsSummary.append("sa.account_type_enum as accountType, "); - accountsSummary.append("sa.account_balance_derived as accountBalance, "); - accountsSummary.append("sa.on_hold_funds_derived as onHoldFunds, "); - accountsSummary.append("sa.total_savings_amount_on_hold as onHoldAmount, "); - - accountsSummary.append("sa.submittedon_date as submittedOnDate,"); - accountsSummary.append("sbu.username as submittedByUsername,"); - accountsSummary.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); - - accountsSummary.append("sa.rejectedon_date as rejectedOnDate,"); - accountsSummary.append("rbu.username as rejectedByUsername,"); - accountsSummary.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,"); - - accountsSummary.append("sa.withdrawnon_date as withdrawnOnDate,"); - accountsSummary.append("wbu.username as withdrawnByUsername,"); - accountsSummary.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,"); - - accountsSummary.append("sa.approvedon_date as approvedOnDate,"); - accountsSummary.append("abu.username as approvedByUsername,"); - accountsSummary.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,"); - - accountsSummary.append("sa.activatedon_date as activatedOnDate,"); - accountsSummary.append("avbu.username as activatedByUsername,"); - accountsSummary.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,"); - - accountsSummary.append("sa.sub_status_enum as subStatusEnum, "); - accountsSummary.append("(select coalesce(max(sat.transaction_date),sa.activatedon_date) "); - accountsSummary.append("from m_savings_account_transaction as sat "); - accountsSummary.append("where sat.is_reversed = false "); - accountsSummary.append("and sat.transaction_type_enum in (1,2) "); - accountsSummary.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, "); - - accountsSummary.append("sa.closedon_date as closedOnDate,"); - accountsSummary.append("cbu.username as closedByUsername,"); - accountsSummary.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); - - accountsSummary.append( - "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); - accountsSummary.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - accountsSummary.append("curr.display_symbol as currencyDisplaySymbol, "); - accountsSummary.append("sa.product_id as productId, p.name as productName, p.short_name as shortProductName, "); - accountsSummary.append("sa.deposit_type_enum as depositType "); - accountsSummary.append("from m_savings_account sa "); - accountsSummary.append("join m_savings_product as p on p.id = sa.product_id "); - accountsSummary.append("join m_currency curr on curr.code = sa.currency_code "); - accountsSummary.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid "); - accountsSummary.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid "); - accountsSummary.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid "); - accountsSummary.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); - accountsSummary.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid "); - accountsSummary.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); - - this.schemaSql = accountsSummary.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public SavingsAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = JdbcSupport.getLong(rs, "id"); - final String accountNo = rs.getString("accountNo"); - final String externalId = rs.getString("externalId"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - final String productName = rs.getString("productName"); - final String shortProductName = rs.getString("shortProductName"); - final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); - final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance"); - final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds"); - final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldAmount"); - - BigDecimal availableBalance = accountBalance; - if (availableBalance != null && onHoldFunds != null) { - availableBalance = availableBalance.subtract(onHoldFunds); - } - - if (availableBalance != null && onHoldAmount != null) { - availableBalance = availableBalance.subtract(onHoldAmount); - } - - final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId); - final Integer accountType = JdbcSupport.getInteger(rs, "accountType"); - final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType); - final Integer depositTypeId = JdbcSupport.getInteger(rs, "depositType"); - final EnumOptionData depositTypeData = SavingsEnumerations.depositType(depositTypeId); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); - final String submittedByUsername = rs.getString("submittedByUsername"); - final String submittedByFirstname = rs.getString("submittedByFirstname"); - final String submittedByLastname = rs.getString("submittedByLastname"); - - final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); - final String rejectedByUsername = rs.getString("rejectedByUsername"); - final String rejectedByFirstname = rs.getString("rejectedByFirstname"); - final String rejectedByLastname = rs.getString("rejectedByLastname"); - - final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); - final String withdrawnByUsername = rs.getString("withdrawnByUsername"); - final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); - final String withdrawnByLastname = rs.getString("withdrawnByLastname"); - - final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); - final String approvedByUsername = rs.getString("approvedByUsername"); - final String approvedByFirstname = rs.getString("approvedByFirstname"); - final String approvedByLastname = rs.getString("approvedByLastname"); - - final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); - final String activatedByUsername = rs.getString("activatedByUsername"); - final String activatedByFirstname = rs.getString("activatedByFirstname"); - final String activatedByLastname = rs.getString("activatedByLastname"); - - final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); - final String closedByUsername = rs.getString("closedByUsername"); - final String closedByFirstname = rs.getString("closedByFirstname"); - final String closedByLastname = rs.getString("closedByLastname"); - final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); - final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); - - final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate"); - - final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, - submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, - rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, - approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, activatedByUsername, activatedByFirstname, - activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname); - - return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, - accountBalance, onHoldFunds, onHoldAmount, availableBalance, accountTypeData, timeline, depositTypeData, subStatus, - lastActiveTransactionDate); - } - } - - private static final class CurrentAccountSummaryDataMapper implements RowMapper { - - private final String schemaSql; - - CurrentAccountSummaryDataMapper() { - final StringBuilder sb = new StringBuilder(); - sb.append("ca.id as id, ca.account_no as accountNo, ca.external_id as externalId, "); - sb.append("ca.status_enum as statusEnum, ca.sub_status_enum as subStatusEnum, "); - sb.append("ca.account_type_enum as accountType, "); - sb.append("ca.account_balance_derived as accountBalance, "); - sb.append("ca.on_hold_funds_derived as onHoldFunds, "); - sb.append("ca.submittedon_date as submittedOnDate, "); - sb.append("sbu.username as submittedByUsername, sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname, "); - sb.append("ca.rejectedon_date as rejectedOnDate, "); - sb.append("rbu.username as rejectedByUsername, rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname, "); - sb.append("ca.withdrawnon_date as withdrawnOnDate, "); - sb.append("wbu.username as withdrawnByUsername, wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname, "); - sb.append("ca.approvedon_date as approvedOnDate, "); - sb.append("abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname, "); - sb.append("ca.activatedon_date as activatedOnDate, "); - sb.append("avbu.username as activatedByUsername, avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname, "); - sb.append("ca.closedon_date as closedOnDate, "); - sb.append("cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, "); - sb.append("ca.currency_code as currencyCode, ca.currency_digits as currencyDigits, ca.currency_multiplesof as inMultiplesOf, "); - sb.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - sb.append("curr.display_symbol as currencyDisplaySymbol, "); - sb.append("ca.product_id as productId, cp.name as productName, cp.short_name as shortProductName "); - sb.append("from m_current_account ca "); - sb.append("join m_current_product cp on cp.id = ca.product_id "); - sb.append("join m_currency curr on curr.code = ca.currency_code "); - sb.append("left join m_appuser sbu on sbu.id = ca.submittedon_userid "); - sb.append("left join m_appuser rbu on rbu.id = ca.rejectedon_userid "); - sb.append("left join m_appuser wbu on wbu.id = ca.withdrawnon_userid "); - sb.append("left join m_appuser abu on abu.id = ca.approvedon_userid "); - sb.append("left join m_appuser avbu on avbu.id = ca.activatedon_userid "); - sb.append("left join m_appuser cbu on cbu.id = ca.closedon_userid "); - this.schemaSql = sb.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public SavingsAccountSummaryData mapRow(final ResultSet rs, final int rowNum) throws SQLException { - final Long id = JdbcSupport.getLong(rs, "id"); - final String accountNo = rs.getString("accountNo"); - final String externalId = rs.getString("externalId"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - final String productName = rs.getString("productName"); - final String shortProductName = rs.getString("shortProductName"); - final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); - final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId); - final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); - final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); - final Integer accountType = JdbcSupport.getInteger(rs, "accountType"); - final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType); - final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance"); - final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds"); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, - currencyDisplaySymbol, currencyNameCode); - - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); - final String submittedByUsername = rs.getString("submittedByUsername"); - final String submittedByFirstname = rs.getString("submittedByFirstname"); - final String submittedByLastname = rs.getString("submittedByLastname"); - - final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); - final String rejectedByUsername = rs.getString("rejectedByUsername"); - final String rejectedByFirstname = rs.getString("rejectedByFirstname"); - final String rejectedByLastname = rs.getString("rejectedByLastname"); - - final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); - final String withdrawnByUsername = rs.getString("withdrawnByUsername"); - final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); - final String withdrawnByLastname = rs.getString("withdrawnByLastname"); - - final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); - final String approvedByUsername = rs.getString("approvedByUsername"); - final String approvedByFirstname = rs.getString("approvedByFirstname"); - final String approvedByLastname = rs.getString("approvedByLastname"); - - final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); - final String activatedByUsername = rs.getString("activatedByUsername"); - final String activatedByFirstname = rs.getString("activatedByFirstname"); - final String activatedByLastname = rs.getString("activatedByLastname"); - - final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); - final String closedByUsername = rs.getString("closedByUsername"); - final String closedByFirstname = rs.getString("closedByFirstname"); - final String closedByLastname = rs.getString("closedByLastname"); - - final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, - submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, - rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, - withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, - activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, - closedByLastname); - - final EnumOptionData depositTypeData = SavingsEnumerations.depositType(400); - - return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, - accountBalance, onHoldFunds, null, accountBalance, accountTypeData, timeline, depositTypeData, subStatus, activatedOnDate); - } - } - - private static final class LoanAccountSummaryDataMapper implements RowMapper { - - public String loanAccountSummarySchema() { - - final StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,"); - 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(" glim.account_number as parentAccountNumber,") - - .append("l.principal_disbursed_derived as originalLoan,").append("l.total_outstanding_derived as loanBalance,") - .append("l.total_repayment_derived as amountPaid,") - - .append(" l.loan_product_counter as loanCycle,") - - .append("l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf,") - - .append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode,") - .append("curr.display_symbol as currencyDisplaySymbol,") - - .append(" l.submittedon_date as submittedOnDate,") - .append(" sbu.username as submittedByUsername, sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,") - - .append(" l.rejectedon_date as rejectedOnDate,") - .append(" rbu.username as rejectedByUsername, rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,") - - .append(" l.withdrawnon_date as withdrawnOnDate,") - .append(" wbu.username as withdrawnByUsername, wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,") - - .append(" l.approvedon_date as approvedOnDate,") - .append(" abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,") - - .append(" l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate,") - .append(" dbu.username as disbursedByUsername, dbu.firstname as disbursedByFirstname, dbu.lastname as disbursedByLastname,") - - .append(" l.closedon_date as closedOnDate,") - .append(" cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,") - .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.charged_off_on_date as chargedOffOnDate, cobu.username as chargedOffByUsername, ") - .append(" cobu.firstname as chargedOffByFirstname, cobu.lastname as chargedOffByLastname ").append(" from m_loan l ") - .append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id") - .append(" left join m_currency curr on curr.code = l.currency_code") - .append(" left join m_appuser sbu on sbu.id = l.created_by") - .append(" left join m_appuser rbu on rbu.id = l.rejectedon_userid") - .append(" left join m_appuser wbu on wbu.id = l.withdrawnon_userid") - .append(" left join m_appuser abu on abu.id = l.approvedon_userid") - .append(" left join m_appuser dbu on dbu.id = l.disbursedon_userid") - .append(" left join m_appuser cbu on cbu.id = l.closedon_userid") - .append(" left join m_appuser cobu on cobu.id = l.charged_off_by_userid") - .append(" left join m_loan_arrears_aging la on la.loan_id = l.id") - .append(" left join glim_accounts glim on glim.id=l.glim_id"); - - return accountsSummary.toString(); - } - - @Override - public LoanAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = JdbcSupport.getLong(rs, "id"); - final String accountNo = rs.getString("accountNo"); - final String parentAccountNumber = rs.getString("parentAccountNumber"); - final String externalId = rs.getString("externalId"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - final String loanProductName = rs.getString("productName"); - final String shortLoanProductName = rs.getString("shortProductName"); - final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId"); - final LoanStatusEnumData loanStatus = LoanEnumerations.status(loanStatusId); - final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType"); - final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId); - final Integer loanCycle = JdbcSupport.getInteger(rs, "loanCycle"); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); - final String submittedByUsername = rs.getString("submittedByUsername"); - final String submittedByFirstname = rs.getString("submittedByFirstname"); - final String submittedByLastname = rs.getString("submittedByLastname"); - - final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); - final String rejectedByUsername = rs.getString("rejectedByUsername"); - final String rejectedByFirstname = rs.getString("rejectedByFirstname"); - final String rejectedByLastname = rs.getString("rejectedByLastname"); - - final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); - final String withdrawnByUsername = rs.getString("withdrawnByUsername"); - final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); - final String withdrawnByLastname = rs.getString("withdrawnByLastname"); - - final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); - final String approvedByUsername = rs.getString("approvedByUsername"); - final String approvedByFirstname = rs.getString("approvedByFirstname"); - final String approvedByLastname = rs.getString("approvedByLastname"); - - final LocalDate expectedDisbursementDate = JdbcSupport.getLocalDate(rs, "expectedDisbursementDate"); - final LocalDate actualDisbursementDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate"); - final String disbursedByUsername = rs.getString("disbursedByUsername"); - final String disbursedByFirstname = rs.getString("disbursedByFirstname"); - final String disbursedByLastname = rs.getString("disbursedByLastname"); - - final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); - final String closedByUsername = rs.getString("closedByUsername"); - final String closedByFirstname = rs.getString("closedByFirstname"); - final String closedByLastname = rs.getString("closedByLastname"); - - final BigDecimal originalLoan = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "originalLoan"); - final BigDecimal loanBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "loanBalance"); - final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountPaid"); - - final LocalDate writtenOffOnDate = JdbcSupport.getLocalDate(rs, "writtenOffOnDate"); - - final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate"); - final LocalDate actualMaturityDate = JdbcSupport.getLocalDate(rs, "actualMaturityDate"); - - final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate"); - - final LocalDate chargedOffOnDate = JdbcSupport.getLocalDate(rs, "chargedOffOnDate"); - final String chargedOffByUsername = rs.getString("chargedOffByUsername"); - final String chargedOffByFirstname = rs.getString("chargedOffByFirstname"); - final String chargedOffByLastname = rs.getString("chargedOffByLastname"); - - Boolean inArrears = (overdueSinceDate != null); - - final LoanApplicationTimelineData timeline = new LoanApplicationTimelineData(submittedOnDate, submittedByUsername, - submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname, - withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, - approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername, - disbursedByFirstname, disbursedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname, - actualMaturityDate, expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname, - chargedOffOnDate, chargedOffByUsername, chargedOffByFirstname, chargedOffByLastname); - - return new LoanAccountSummaryData(id, accountNo, parentAccountNumber, externalId, productId, loanProductName, - shortLoanProductName, loanStatus, currency, loanType, loanCycle, timeline, inArrears, originalLoan, loanBalance, - amountPaid); - } - - } - - private static final class GuarantorLoanAccountSummaryDataMapper implements RowMapper { - - public String guarantorLoanAccountSummarySchema() { - - final StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,"); - 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.principal_disbursed_derived as originalLoan,").append("l.total_outstanding_derived as loanBalance,") - .append("l.total_repayment_derived as amountPaid,") - - .append(" l.loan_product_counter as loanCycle,") - - .append(" l.submittedon_date as submittedOnDate,") - - .append(" l.rejectedon_date as rejectedOnDate,").append(" l.withdrawnon_date as withdrawnOnDate,") - .append(" l.approvedon_date as approvedOnDate,") - .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.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(" 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(" 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_savings_account sa on sa.client_id = c.id"); - - return accountsSummary.toString(); - } - - @Override - public GuarantorAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = JdbcSupport.getLong(rs, "id"); - final String accountNo = rs.getString("accountNo"); - final String externalId = rs.getString("externalId"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - final String loanProductName = rs.getString("productName"); - final String shortLoanProductName = rs.getString("shortProductName"); - final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId"); - final LoanStatusEnumData loanStatus = LoanEnumerations.status(loanStatusId); - final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType"); - final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId); - final Integer loanCycle = JdbcSupport.getInteger(rs, "loanCycle"); - - final BigDecimal originalLoan = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "originalLoan"); - final BigDecimal loanBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "loanBalance"); - final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountPaid"); - final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "on_hold_funds_derived"); - - final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate"); - Boolean inArrears = true; - if (overdueSinceDate == null) { - inArrears = false; - } - - final Boolean isActive = rs.getBoolean("isActive"); - - final String relationship = rs.getString("relationship"); - return new GuarantorAccountSummaryData(id, accountNo, externalId, productId, loanProductName, shortLoanProductName, loanStatus, - loanType, loanCycle, inArrears, originalLoan, loanBalance, amountPaid, isActive, relationship, onHoldAmount); - } - - } - -} +/** + * 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.accountdetails.service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; +import lombok.AllArgsConstructor; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData; +import org.apache.fineract.portfolio.accountdetails.data.GuarantorAccountSummaryData; +import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData; +import org.apache.fineract.portfolio.accountdetails.data.SavingsAccountSummaryData; +import org.apache.fineract.portfolio.accountdetails.data.ShareAccountSummaryData; +import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; +import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData; +import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData; +import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; +import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData; +import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountApplicationTimelineData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountStatusEnumData; +import org.apache.fineract.portfolio.shareaccounts.service.SharesEnumerations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements AccountDetailsReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + private final ClientReadPlatformService clientReadPlatformService; + private final GroupReadPlatformService groupReadPlatformService; + private final ColumnValidator columnValidator; + + @Override + public AccountSummaryCollectionData retrieveClientAccountDetails(final Long clientId) { + // Check if client exists + this.clientReadPlatformService.retrieveOne(clientId); + final String loanwhereClause = " where l.client_id = ?"; + final String glimLoanClause = " where l.client_id = ? and l.loan_type_enum=4"; + + // note to self: investigate the effect of have loans stored as another + // type especially given that we want to maintain how the system + // currently handles loans of exisiting types. + final String savingswhereClause = " where sa.client_id = ? order by sa.status_enum ASC, sa.account_no ASC"; + + final String guarantorWhereClause = " where g.entity_id = ? and g.is_active = true order by l.account_no ASC"; + + final List glimAccounts = retrieveLoanAccountDetails(glimLoanClause, new Object[] { clientId }); + final List loanAccounts = retrieveLoanAccountDetails(loanwhereClause, new Object[] { clientId }); + final List savingsAccounts = retrieveAccountDetails(savingswhereClause, new Object[] { clientId }); + final List shareAccounts = retrieveShareAccountDetails(clientId); + final List guarantorloanAccounts = retrieveGuarantorLoanAccountDetails(guarantorWhereClause, + new Object[] { clientId }); + final List currentAccounts = retrieveCurrentAccountDetails(clientId); + return new AccountSummaryCollectionData(loanAccounts, glimAccounts, savingsAccounts, shareAccounts, guarantorloanAccounts, currentAccounts); + } + + @Override + public AccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId) { + // Check if group exists + this.groupReadPlatformService.retrieveOne(groupId); + final String loanWhereClauseForGroup = " where l.group_id = ? and l.client_id is null"; + final String loanWhereClauseForGroupAndLoanType = " where l.group_id = ? and l.loan_type_enum=4"; + final String loanWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null"; + final String savingswhereClauseForGroup = " where sa.group_id = ? and sa.client_id is null order by sa.status_enum ASC, sa.account_no ASC"; + final String savingswhereClauseForMembers = " where sa.group_id = ? and sa.client_id is not null order by sa.status_enum ASC, sa.account_no ASC"; + + final String guarantorWhereClauseForGroup = " where l.group_id = ? and l.client_id is null and g.is_active = true order by l.account_no ASC"; + final String guarantorWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null and g.is_active = true order by l.account_no ASC"; + final List glimAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroupAndLoanType, + new Object[] { groupId }); + + final List groupLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroup, + new Object[] { groupId }); + final List groupSavingsAccounts = retrieveAccountDetails(savingswhereClauseForGroup, + new Object[] { groupId }); + final List groupGuarantorloanAccounts = retrieveGuarantorLoanAccountDetails( + guarantorWhereClauseForGroup, new Object[] { groupId }); + final List memberLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForMembers, + new Object[] { groupId }); + final List memberSavingsAccounts = retrieveAccountDetails(savingswhereClauseForMembers, + new Object[] { groupId }); + + final List memberGuarantorloanAccounts = retrieveGuarantorLoanAccountDetails( + guarantorWhereClauseForMembers, new Object[] { groupId }); + return new AccountSummaryCollectionData(groupLoanAccounts, glimAccounts, groupSavingsAccounts, groupGuarantorloanAccounts, + memberLoanAccounts, memberSavingsAccounts, memberGuarantorloanAccounts); + + } + + @Override + public AccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId, final Long gsimId) { + // Check if group exists + this.groupReadPlatformService.retrieveOne(groupId); + final String loanWhereClauseForGroup = " where l.group_id = ? and l.client_id is null"; + final String loanWhereClauseForGroupAndLoanType = " where l.group_id = ? and l.loan_type_enum=4"; + final String loanWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null"; + final String savingswhereClauseForGroup = " where sa.group_id = ? and sa.gsim_id = ? sa.client_id is null order by sa.status_enum ASC, sa.account_no ASC"; + final String savingswhereClauseForMembers = " where sa.group_id = ? and sa.client_id is not null order by sa.status_enum ASC, sa.account_no ASC"; + + final List glimAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroupAndLoanType, + new Object[] { groupId }); + final List groupLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroup, + new Object[] { groupId }); + final List gsimSavingsAccounts = retrieveAccountDetails(savingswhereClauseForGroup, + new Object[] { groupId, gsimId }); + final List memberLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForMembers, + new Object[] { groupId }); + final List memberSavingsAccounts = retrieveAccountDetails(savingswhereClauseForMembers, + new Object[] { groupId }); + return new AccountSummaryCollectionData(groupLoanAccounts, glimAccounts, gsimSavingsAccounts, null, memberLoanAccounts, + memberSavingsAccounts, null); + } + + @Override + public Collection retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId) { + // Check if client exists + this.clientReadPlatformService.retrieveOne(clientId); + final String loanWhereClause = " where l.client_id = ? and l.loan_officer_id = ?"; + return retrieveLoanAccountDetails(loanWhereClause, new Object[] { clientId, loanOfficerId }); + } + + @Override + public Collection retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId, final Long loanOfficerId) { + // Check if group exists + this.groupReadPlatformService.retrieveOne(groupId); + final String loanWhereClause = " where l.group_id = ? and l.client_id is null and l.loan_officer_id = ?"; + return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId, loanOfficerId }); + } + + @Override + public Collection retrieveClientActiveLoanAccountSummary(final Long clientId) { + final String loanWhereClause = " where l.client_id = ? and l.loan_status_id = 300 "; + return retrieveLoanAccountDetails(loanWhereClause, new Object[] { clientId }); + } + + @Override + public List retrieveLoanAccountDetailsByGroupIdAndGlimAccountNumber(final Long groupId, + final String glimAccount) { + final LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper(); + final String loanWhereClauseForGroupAndLoanType = " where l.group_id =? and glim.account_number=? and l.loan_type_enum=4"; + final String sql = "select " + rm.loanAccountSummarySchema() + loanWhereClauseForGroupAndLoanType; + return this.jdbcTemplate.query(sql, rm, new Object[] { groupId, glimAccount }); // NOSONAR + } + + @Override + public Collection retrieveGroupActiveLoanAccountSummary(final Long groupId) { + final String loanWhereClause = " where l.group_id = ? and l.loan_status_id = 300 and l.client_id is null"; + return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId }); + } + + private List retrieveCurrentAccountDetails(final Long clientId) { + final CurrentAccountSummaryDataMapper mapper = new CurrentAccountSummaryDataMapper(); + final String sql = "select " + mapper.schema() + " where ca.client_id = ? order by ca.status_enum ASC, ca.account_no ASC"; + return this.jdbcTemplate.query(sql, mapper, new Object[] { clientId }); // NOSONAR + } + + private List retrieveLoanAccountDetails(final String loanwhereClause, final Object[] inputs) { + final LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper(); + final String sql = "select " + rm.loanAccountSummarySchema() + loanwhereClause; + this.columnValidator.validateSqlInjection(rm.loanAccountSummarySchema(), loanwhereClause); + return this.jdbcTemplate.query(sql, rm, inputs); // NOSONAR + } + + /** + * @param entityId + * @return + */ + private List retrieveAccountDetails(final String savingswhereClause, final Object[] inputs) { + final SavingsAccountSummaryDataMapper savingsAccountSummaryDataMapper = new SavingsAccountSummaryDataMapper(); + final String savingsSql = "select " + savingsAccountSummaryDataMapper.schema() + savingswhereClause; + this.columnValidator.validateSqlInjection(savingsAccountSummaryDataMapper.schema(), savingswhereClause); + return this.jdbcTemplate.query(savingsSql, savingsAccountSummaryDataMapper, inputs); // NOSONAR + } + + private List retrieveShareAccountDetails(final Long clientId) { + final ShareAccountSummaryDataMapper mapper = new ShareAccountSummaryDataMapper(); + final String query = "select " + mapper.schema() + " where sa.client_id = ?"; + return this.jdbcTemplate.query(query, mapper, new Object[] { clientId }); // NOSONAR + } + + private List retrieveGuarantorLoanAccountDetails(final String loanwhereClause, final Object[] inputs) { + final GuarantorLoanAccountSummaryDataMapper rm = new GuarantorLoanAccountSummaryDataMapper(); + final String sql = "select " + rm.guarantorLoanAccountSummarySchema() + loanwhereClause; + return this.jdbcTemplate.query(sql, rm, inputs); // NOSONAR + } + + private static final class ShareAccountSummaryDataMapper implements RowMapper { + + private final String schema; + + ShareAccountSummaryDataMapper() { + final StringBuilder buff = new StringBuilder() + .append("sa.id as id, sa.external_id as externalId, sa.status_enum as statusEnum, ") + .append("sa.account_no as accountNo, sa.total_approved_shares as approvedShares, sa.total_pending_shares as pendingShares, ") + .append("sa.savings_account_id as savingsAccountNo, sa.minimum_active_period_frequency as minimumactivePeriod,") + .append("sa.minimum_active_period_frequency_enum as minimumactivePeriodEnum,") + .append("sa.lockin_period_frequency as lockinPeriod, sa.lockin_period_frequency_enum as lockinPeriodEnum, ") + .append("sa.submitted_date as submittedDate, sbu.username as submittedByUsername, ") + .append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname, ") + .append("sa.rejected_date as rejectedDate, rbu.username as rejectedByUsername, ") + .append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname, ") + .append("sa.approved_date as approvedDate, abu.username as approvedByUsername, ") + .append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname, ") + .append("sa.activated_date as activatedDate, avbu.username as activatedByUsername, ") + .append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname, ") + .append("sa.closed_date as closedDate, cbu.username as closedByUsername, ") + .append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, ") + .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ") + .append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ") + .append("curr.display_symbol as currencyDisplaySymbol, sa.product_id as productId, p.name as productName, p.short_name as shortProductName ") + .append("from m_share_account sa ").append("join m_share_product as p on p.id = sa.product_id ") + .append("join m_currency curr on curr.code = sa.currency_code ") + .append("left join m_appuser sbu on sbu.id = sa.submitted_userid ") + .append("left join m_appuser rbu on rbu.id = sa.rejected_userid ") + .append("left join m_appuser abu on abu.id = sa.approved_userid ") + .append("left join m_appuser avbu on avbu.id = sa.activated_userid ") + .append("left join m_appuser cbu on cbu.id = sa.closed_userid "); + schema = buff.toString(); + } + + @Override + public ShareAccountSummaryData mapRow(ResultSet rs, int rowNum) throws SQLException { + + final Long id = JdbcSupport.getLong(rs, "id"); + final String accountNo = rs.getString("accountNo"); + final Long approvedShares = JdbcSupport.getLong(rs, "approvedShares"); + final Long pendingShares = JdbcSupport.getLong(rs, "pendingShares"); + final String externalId = rs.getString("externalId"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final String productName = rs.getString("productName"); + final String shortProductName = rs.getString("shortProductName"); + final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); + final ShareAccountStatusEnumData status = SharesEnumerations.status(statusId); + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + final String submittedByFirstname = rs.getString("submittedByFirstname"); + final String submittedByLastname = rs.getString("submittedByLastname"); + + final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedDate"); + final String rejectedByUsername = rs.getString("rejectedByUsername"); + final String rejectedByFirstname = rs.getString("rejectedByFirstname"); + final String rejectedByLastname = rs.getString("rejectedByLastname"); + + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedDate"); + final String approvedByUsername = rs.getString("approvedByUsername"); + final String approvedByFirstname = rs.getString("approvedByFirstname"); + final String approvedByLastname = rs.getString("approvedByLastname"); + + final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedDate"); + final String activatedByUsername = rs.getString("activatedByUsername"); + final String activatedByFirstname = rs.getString("activatedByFirstname"); + final String activatedByLastname = rs.getString("activatedByLastname"); + + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedDate"); + final String closedByUsername = rs.getString("closedByUsername"); + final String closedByFirstname = rs.getString("closedByFirstname"); + final String closedByLastname = rs.getString("closedByLastname"); + + final ShareAccountApplicationTimelineData timeline = new ShareAccountApplicationTimelineData(submittedOnDate, + submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, + rejectedByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, + activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, + closedByLastname); + + return new ShareAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, + approvedShares, pendingShares, timeline); + } + + public String schema() { + return this.schema; + } + } + + private static final class SavingsAccountSummaryDataMapper implements RowMapper { + + final String schemaSql; + + SavingsAccountSummaryDataMapper() { + final StringBuilder accountsSummary = new StringBuilder(); + accountsSummary.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, sa.status_enum as statusEnum, "); + accountsSummary.append("sa.account_type_enum as accountType, "); + accountsSummary.append("sa.account_balance_derived as accountBalance, "); + accountsSummary.append("sa.on_hold_funds_derived as onHoldFunds, "); + accountsSummary.append("sa.total_savings_amount_on_hold as onHoldAmount, "); + + accountsSummary.append("sa.submittedon_date as submittedOnDate,"); + accountsSummary.append("sbu.username as submittedByUsername,"); + accountsSummary.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); + + accountsSummary.append("sa.rejectedon_date as rejectedOnDate,"); + accountsSummary.append("rbu.username as rejectedByUsername,"); + accountsSummary.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,"); + + accountsSummary.append("sa.withdrawnon_date as withdrawnOnDate,"); + accountsSummary.append("wbu.username as withdrawnByUsername,"); + accountsSummary.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,"); + + accountsSummary.append("sa.approvedon_date as approvedOnDate,"); + accountsSummary.append("abu.username as approvedByUsername,"); + accountsSummary.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,"); + + accountsSummary.append("sa.activatedon_date as activatedOnDate,"); + accountsSummary.append("avbu.username as activatedByUsername,"); + accountsSummary.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,"); + + accountsSummary.append("sa.sub_status_enum as subStatusEnum, "); + accountsSummary.append("(select coalesce(max(sat.transaction_date),sa.activatedon_date) "); + accountsSummary.append("from m_savings_account_transaction as sat "); + accountsSummary.append("where sat.is_reversed = false "); + accountsSummary.append("and sat.transaction_type_enum in (1,2) "); + accountsSummary.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, "); + + accountsSummary.append("sa.closedon_date as closedOnDate,"); + accountsSummary.append("cbu.username as closedByUsername,"); + accountsSummary.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); + + accountsSummary.append( + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); + accountsSummary.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + accountsSummary.append("curr.display_symbol as currencyDisplaySymbol, "); + accountsSummary.append("sa.product_id as productId, p.name as productName, p.short_name as shortProductName, "); + accountsSummary.append("sa.deposit_type_enum as depositType "); + accountsSummary.append("from m_savings_account sa "); + accountsSummary.append("join m_savings_product as p on p.id = sa.product_id "); + accountsSummary.append("join m_currency curr on curr.code = sa.currency_code "); + accountsSummary.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid "); + accountsSummary.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid "); + accountsSummary.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid "); + accountsSummary.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); + accountsSummary.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid "); + accountsSummary.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); + + this.schemaSql = accountsSummary.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = JdbcSupport.getLong(rs, "id"); + final String accountNo = rs.getString("accountNo"); + final String externalId = rs.getString("externalId"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final String productName = rs.getString("productName"); + final String shortProductName = rs.getString("shortProductName"); + final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); + final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance"); + final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds"); + final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldAmount"); + + BigDecimal availableBalance = accountBalance; + if (availableBalance != null && onHoldFunds != null) { + availableBalance = availableBalance.subtract(onHoldFunds); + } + + if (availableBalance != null && onHoldAmount != null) { + availableBalance = availableBalance.subtract(onHoldAmount); + } + + final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId); + final Integer accountType = JdbcSupport.getInteger(rs, "accountType"); + final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType); + final Integer depositTypeId = JdbcSupport.getInteger(rs, "depositType"); + final EnumOptionData depositTypeData = SavingsEnumerations.depositType(depositTypeId); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + final String submittedByFirstname = rs.getString("submittedByFirstname"); + final String submittedByLastname = rs.getString("submittedByLastname"); + + final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); + final String rejectedByUsername = rs.getString("rejectedByUsername"); + final String rejectedByFirstname = rs.getString("rejectedByFirstname"); + final String rejectedByLastname = rs.getString("rejectedByLastname"); + + final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); + final String withdrawnByUsername = rs.getString("withdrawnByUsername"); + final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); + final String withdrawnByLastname = rs.getString("withdrawnByLastname"); + + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); + final String approvedByUsername = rs.getString("approvedByUsername"); + final String approvedByFirstname = rs.getString("approvedByFirstname"); + final String approvedByLastname = rs.getString("approvedByLastname"); + + final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); + final String activatedByUsername = rs.getString("activatedByUsername"); + final String activatedByFirstname = rs.getString("activatedByFirstname"); + final String activatedByLastname = rs.getString("activatedByLastname"); + + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final String closedByUsername = rs.getString("closedByUsername"); + final String closedByFirstname = rs.getString("closedByFirstname"); + final String closedByLastname = rs.getString("closedByLastname"); + final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); + final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); + + final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate"); + + final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, + submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, + rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, + approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, activatedByUsername, activatedByFirstname, + activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname); + + return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, + accountBalance, onHoldFunds, onHoldAmount, availableBalance, accountTypeData, timeline, depositTypeData, subStatus, + lastActiveTransactionDate); + } + } + + private static final class CurrentAccountSummaryDataMapper implements RowMapper { + + private final String schemaSql; + + CurrentAccountSummaryDataMapper() { + final StringBuilder sb = new StringBuilder(); + sb.append("ca.id as id, ca.account_no as accountNo, ca.external_id as externalId, "); + sb.append("ca.status_enum as statusEnum, ca.sub_status_enum as subStatusEnum, "); + sb.append("ca.account_type_enum as accountType, "); + sb.append("ca.account_balance_derived as accountBalance, "); + sb.append("ca.on_hold_funds_derived as onHoldFunds, "); + sb.append("ca.submittedon_date as submittedOnDate, "); + sb.append("sbu.username as submittedByUsername, sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname, "); + sb.append("ca.rejectedon_date as rejectedOnDate, "); + sb.append("rbu.username as rejectedByUsername, rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname, "); + sb.append("ca.withdrawnon_date as withdrawnOnDate, "); + sb.append("wbu.username as withdrawnByUsername, wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname, "); + sb.append("ca.approvedon_date as approvedOnDate, "); + sb.append("abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname, "); + sb.append("ca.activatedon_date as activatedOnDate, "); + sb.append("avbu.username as activatedByUsername, avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname, "); + sb.append("ca.closedon_date as closedOnDate, "); + sb.append("cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, "); + sb.append("ca.currency_code as currencyCode, ca.currency_digits as currencyDigits, ca.currency_multiplesof as inMultiplesOf, "); + sb.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + sb.append("curr.display_symbol as currencyDisplaySymbol, "); + sb.append("ca.product_id as productId, cp.name as productName, cp.short_name as shortProductName "); + sb.append("from m_current_account ca "); + sb.append("join m_current_product cp on cp.id = ca.product_id "); + sb.append("join m_currency curr on curr.code = ca.currency_code "); + sb.append("left join m_appuser sbu on sbu.id = ca.submittedon_userid "); + sb.append("left join m_appuser rbu on rbu.id = ca.rejectedon_userid "); + sb.append("left join m_appuser wbu on wbu.id = ca.withdrawnon_userid "); + sb.append("left join m_appuser abu on abu.id = ca.approvedon_userid "); + sb.append("left join m_appuser avbu on avbu.id = ca.activatedon_userid "); + sb.append("left join m_appuser cbu on cbu.id = ca.closedon_userid "); + this.schemaSql = sb.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountSummaryData mapRow(final ResultSet rs, final int rowNum) throws SQLException { + final Long id = JdbcSupport.getLong(rs, "id"); + final String accountNo = rs.getString("accountNo"); + final String externalId = rs.getString("externalId"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final String productName = rs.getString("productName"); + final String shortProductName = rs.getString("shortProductName"); + final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); + final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId); + final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); + final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); + final Integer accountType = JdbcSupport.getInteger(rs, "accountType"); + final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType); + final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance"); + final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + final String submittedByFirstname = rs.getString("submittedByFirstname"); + final String submittedByLastname = rs.getString("submittedByLastname"); + + final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); + final String rejectedByUsername = rs.getString("rejectedByUsername"); + final String rejectedByFirstname = rs.getString("rejectedByFirstname"); + final String rejectedByLastname = rs.getString("rejectedByLastname"); + + final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); + final String withdrawnByUsername = rs.getString("withdrawnByUsername"); + final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); + final String withdrawnByLastname = rs.getString("withdrawnByLastname"); + + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); + final String approvedByUsername = rs.getString("approvedByUsername"); + final String approvedByFirstname = rs.getString("approvedByFirstname"); + final String approvedByLastname = rs.getString("approvedByLastname"); + + final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); + final String activatedByUsername = rs.getString("activatedByUsername"); + final String activatedByFirstname = rs.getString("activatedByFirstname"); + final String activatedByLastname = rs.getString("activatedByLastname"); + + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final String closedByUsername = rs.getString("closedByUsername"); + final String closedByFirstname = rs.getString("closedByFirstname"); + final String closedByLastname = rs.getString("closedByLastname"); + + final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, + submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, + rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, + withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, + activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, + closedByLastname); + + final EnumOptionData depositTypeData = SavingsEnumerations.depositType(400); + + return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, + accountBalance, onHoldFunds, null, accountBalance, accountTypeData, timeline, depositTypeData, subStatus, activatedOnDate); + } + } + + private static final class LoanAccountSummaryDataMapper implements RowMapper { + + public String loanAccountSummarySchema() { + + final StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,"); + 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(" glim.account_number as parentAccountNumber,") + + .append("l.principal_disbursed_derived as originalLoan,").append("l.total_outstanding_derived as loanBalance,") + .append("l.total_repayment_derived as amountPaid,") + + .append(" l.loan_product_counter as loanCycle,") + + .append("l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf,") + + .append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode,") + .append("curr.display_symbol as currencyDisplaySymbol,") + + .append(" l.submittedon_date as submittedOnDate,") + .append(" sbu.username as submittedByUsername, sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,") + + .append(" l.rejectedon_date as rejectedOnDate,") + .append(" rbu.username as rejectedByUsername, rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,") + + .append(" l.withdrawnon_date as withdrawnOnDate,") + .append(" wbu.username as withdrawnByUsername, wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,") + + .append(" l.approvedon_date as approvedOnDate,") + .append(" abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,") + + .append(" l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate,") + .append(" dbu.username as disbursedByUsername, dbu.firstname as disbursedByFirstname, dbu.lastname as disbursedByLastname,") + + .append(" l.closedon_date as closedOnDate,") + .append(" cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,") + .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.charged_off_on_date as chargedOffOnDate, cobu.username as chargedOffByUsername, ") + .append(" cobu.firstname as chargedOffByFirstname, cobu.lastname as chargedOffByLastname ").append(" from m_loan l ") + .append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id") + .append(" left join m_currency curr on curr.code = l.currency_code") + .append(" left join m_appuser sbu on sbu.id = l.created_by") + .append(" left join m_appuser rbu on rbu.id = l.rejectedon_userid") + .append(" left join m_appuser wbu on wbu.id = l.withdrawnon_userid") + .append(" left join m_appuser abu on abu.id = l.approvedon_userid") + .append(" left join m_appuser dbu on dbu.id = l.disbursedon_userid") + .append(" left join m_appuser cbu on cbu.id = l.closedon_userid") + .append(" left join m_appuser cobu on cobu.id = l.charged_off_by_userid") + .append(" left join m_loan_arrears_aging la on la.loan_id = l.id") + .append(" left join glim_accounts glim on glim.id=l.glim_id"); + + return accountsSummary.toString(); + } + + @Override + public LoanAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = JdbcSupport.getLong(rs, "id"); + final String accountNo = rs.getString("accountNo"); + final String parentAccountNumber = rs.getString("parentAccountNumber"); + final String externalId = rs.getString("externalId"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final String loanProductName = rs.getString("productName"); + final String shortLoanProductName = rs.getString("shortProductName"); + final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId"); + final LoanStatusEnumData loanStatus = LoanEnumerations.status(loanStatusId); + final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType"); + final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId); + final Integer loanCycle = JdbcSupport.getInteger(rs, "loanCycle"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + final String submittedByFirstname = rs.getString("submittedByFirstname"); + final String submittedByLastname = rs.getString("submittedByLastname"); + + final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); + final String rejectedByUsername = rs.getString("rejectedByUsername"); + final String rejectedByFirstname = rs.getString("rejectedByFirstname"); + final String rejectedByLastname = rs.getString("rejectedByLastname"); + + final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); + final String withdrawnByUsername = rs.getString("withdrawnByUsername"); + final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); + final String withdrawnByLastname = rs.getString("withdrawnByLastname"); + + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); + final String approvedByUsername = rs.getString("approvedByUsername"); + final String approvedByFirstname = rs.getString("approvedByFirstname"); + final String approvedByLastname = rs.getString("approvedByLastname"); + + final LocalDate expectedDisbursementDate = JdbcSupport.getLocalDate(rs, "expectedDisbursementDate"); + final LocalDate actualDisbursementDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate"); + final String disbursedByUsername = rs.getString("disbursedByUsername"); + final String disbursedByFirstname = rs.getString("disbursedByFirstname"); + final String disbursedByLastname = rs.getString("disbursedByLastname"); + + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final String closedByUsername = rs.getString("closedByUsername"); + final String closedByFirstname = rs.getString("closedByFirstname"); + final String closedByLastname = rs.getString("closedByLastname"); + + final BigDecimal originalLoan = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "originalLoan"); + final BigDecimal loanBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "loanBalance"); + final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountPaid"); + + final LocalDate writtenOffOnDate = JdbcSupport.getLocalDate(rs, "writtenOffOnDate"); + + final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate"); + final LocalDate actualMaturityDate = JdbcSupport.getLocalDate(rs, "actualMaturityDate"); + + final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate"); + + final LocalDate chargedOffOnDate = JdbcSupport.getLocalDate(rs, "chargedOffOnDate"); + final String chargedOffByUsername = rs.getString("chargedOffByUsername"); + final String chargedOffByFirstname = rs.getString("chargedOffByFirstname"); + final String chargedOffByLastname = rs.getString("chargedOffByLastname"); + + Boolean inArrears = (overdueSinceDate != null); + + final LoanApplicationTimelineData timeline = new LoanApplicationTimelineData(submittedOnDate, submittedByUsername, + submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname, + withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, + approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername, + disbursedByFirstname, disbursedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname, + actualMaturityDate, expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname, + chargedOffOnDate, chargedOffByUsername, chargedOffByFirstname, chargedOffByLastname); + + return new LoanAccountSummaryData(id, accountNo, parentAccountNumber, externalId, productId, loanProductName, + shortLoanProductName, loanStatus, currency, loanType, loanCycle, timeline, inArrears, originalLoan, loanBalance, + amountPaid); + } + + } + + private static final class GuarantorLoanAccountSummaryDataMapper implements RowMapper { + + public String guarantorLoanAccountSummarySchema() { + + final StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,"); + 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(" 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.loan_product_counter as loanCycle,") + + .append(" l.submittedon_date as submittedOnDate,") + + .append(" l.rejectedon_date as rejectedOnDate,").append(" l.withdrawnon_date as withdrawnOnDate,") + .append(" l.approvedon_date as approvedOnDate,") + .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.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(" 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(" 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_code_value cv ON cv.id = g.client_reln_cv_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(); + } + + @Override + public GuarantorAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = JdbcSupport.getLong(rs, "id"); + final String accountNo = rs.getString("accountNo"); + final String externalId = rs.getString("externalId"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final String loanProductName = rs.getString("productName"); + final String shortLoanProductName = rs.getString("shortProductName"); + final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId"); + final LoanStatusEnumData loanStatus = LoanEnumerations.status(loanStatusId); + final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType"); + final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId); + final Integer loanCycle = JdbcSupport.getInteger(rs, "loanCycle"); + + final BigDecimal originalLoan = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "originalLoan"); + final BigDecimal loanBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "loanBalance"); + final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountPaid"); + final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "on_hold_funds_derived"); + + final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate"); + Boolean inArrears = true; + if (overdueSinceDate == null) { + inArrears = false; + } + + final Boolean isActive = rs.getBoolean("isActive"); + + 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, + loanType, loanCycle, inArrears, originalLoan, loanBalance, amountPaid, isActive, relationship, onHoldAmount, + savingsAccountNo, amountReleased, borrowerName, totalOverdue, overdueSinceDate, + submittedOnDate, approvedOnDate, disbursedOnDate, expectedMaturityDate, amountRemaining, savingsBalance); + } + + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java index f53fd1cf9..d1bd60de7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java @@ -725,7 +725,10 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService builder.append("c.alternate_dip_verified as alternateDipVerified, "); builder.append("c.alternate_dip_lookup_log_id as alternateDipLookupLogId, "); 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.screening_log_id as screeningLogId, "); builder.append("c.submittedon_date as submittedOnDate, "); builder.append("sbu.username as submittedByUsername, "); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java index 0aa9a33bd..e295c5166 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java @@ -1,214 +1,215 @@ -/** - * 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.loanaccount.guarantor.data; - -import java.io.Serial; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.Collection; -import java.util.List; -import lombok.Getter; -import org.apache.fineract.infrastructure.codes.data.CodeValueData; -import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.organisation.staff.data.StaffData; -import org.apache.fineract.portfolio.account.data.PortfolioAccountData; -import org.apache.fineract.portfolio.client.data.ClientData; -import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType; -import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorEnumerations; - -@Getter -public class GuarantorData implements IGuarantor { - - @Serial - private static final long serialVersionUID = 1L; - - private final Long id; - private final Long loanId; - private final CodeValueData clientRelationshipType; - private final EnumOptionData guarantorType; - - private final String firstname; - private final String lastname; - - /*** Fields for current customers/staff serving as guarantors **/ - private final Long entityId; - private final String externalId; - private final String officeName; - private final LocalDate joinedDate; - - /*** Fields for external persons serving as guarantors ***/ - - private final String addressLine1; - private final String addressLine2; - private final String city; - private final String state; - private final String zip; - private final String country; - private final String mobileNumber; - private final String housePhoneNumber; - private final String comment; - private final LocalDate dob; - private final Collection guarantorFundingDetails; - private final boolean status; - - // template - @SuppressWarnings("unused") - private final List guarantorTypeOptions; - private final Collection allowedClientRelationshipTypes; - private final Collection accountLinkingOptions; - - // import fields - private Integer guarantorTypeId; - private Integer clientRelationshipTypeId; - private Integer savingsId; - private BigDecimal amount; - private transient Long accountId; - private transient Integer rowIndex; - private String dateFormat; - private String locale; - - public static GuarantorData importInstance(Integer guarantorTypeId, Integer clientRelationshipTypeId, Long entityId, String firstname, - String lastname, String addressLine1, String addressLine2, String city, LocalDate dob, String zip, Integer savingsId, - BigDecimal amount, Integer rowIndex, Long accountId, String locale, String dateFormat) { - return new GuarantorData(guarantorTypeId, clientRelationshipTypeId, entityId, firstname, lastname, addressLine1, addressLine2, city, - dob, zip, savingsId, amount, rowIndex, accountId, locale, dateFormat); - } - - private GuarantorData(Integer guarantorTypeId, Integer clientRelationshipTypeId, Long entityId, String firstname, String lastname, - String addressLine1, String addressLine2, String city, LocalDate dob, String zip, Integer savingsId, BigDecimal amount, - Integer rowIndex, Long accountId, String locale, String dateFormat) { - this.rowIndex = rowIndex; - this.firstname = firstname; - this.lastname = lastname; - this.entityId = entityId; - this.addressLine1 = addressLine1; - this.addressLine2 = addressLine2; - this.city = city; - this.zip = zip; - this.dob = dob; - this.guarantorTypeId = guarantorTypeId; - this.clientRelationshipTypeId = clientRelationshipTypeId; - this.savingsId = savingsId; - this.amount = amount; - this.accountId = accountId; - this.dateFormat = dateFormat; - this.locale = locale; - this.clientRelationshipType = null; - this.guarantorType = null; - this.id = null; - this.loanId = null; - this.externalId = null; - this.officeName = null; - this.joinedDate = null; - this.state = null; - this.country = null; - this.mobileNumber = null; - this.housePhoneNumber = null; - this.comment = null; - this.guarantorFundingDetails = null; - this.status = false; - this.guarantorTypeOptions = null; - this.allowedClientRelationshipTypes = null; - this.accountLinkingOptions = null; - } - - public static GuarantorData template(final List guarantorTypeOptions, - final Collection allowedClientRelationshipTypes, Collection accountLinkingOptions) { - final Collection guarantorFundingDetails = null; - final boolean status = false; - return new GuarantorData(null, null, null, null, GuarantorEnumerations.guarantorType(GuarantorType.CUSTOMER), null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, status, guarantorFundingDetails, - guarantorTypeOptions, allowedClientRelationshipTypes, accountLinkingOptions); - } - - public static GuarantorData templateOnTop(final GuarantorData guarantorData, final List guarantorTypeOptions, - final Collection allowedClientRelationshipTypes, Collection accountLinkingOptions) { - return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId, - guarantorData.guarantorType, guarantorData.firstname, guarantorData.lastname, guarantorData.dob, guarantorData.addressLine1, - guarantorData.addressLine2, guarantorData.city, guarantorData.state, guarantorData.zip, guarantorData.country, - guarantorData.mobileNumber, guarantorData.housePhoneNumber, guarantorData.comment, guarantorData.officeName, - guarantorData.joinedDate, guarantorData.externalId, guarantorData.status, guarantorData.guarantorFundingDetails, - guarantorTypeOptions, allowedClientRelationshipTypes, accountLinkingOptions); - } - - public static GuarantorData mergeClientData(final ClientData clientData, final GuarantorData guarantorData) { - return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId, - guarantorData.guarantorType, clientData.getFirstname(), clientData.getLastname(), null, null, null, null, null, null, null, - null, null, null, clientData.getOfficeName(), clientData.getActivationDate(), clientData.getExternalId().getValue(), - guarantorData.status, guarantorData.guarantorFundingDetails, null, guarantorData.allowedClientRelationshipTypes, - guarantorData.accountLinkingOptions); - } - - public static GuarantorData mergeStaffData(final StaffData staffData, final GuarantorData guarantorData) { - return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId, - guarantorData.guarantorType, staffData.getFirstname(), staffData.getLastname(), null, null, null, null, null, null, null, - null, null, null, staffData.getOfficeName(), null, null, guarantorData.status, guarantorData.guarantorFundingDetails, null, - guarantorData.allowedClientRelationshipTypes, guarantorData.accountLinkingOptions); - } - - public GuarantorData(final Long id, final Long loanId, final CodeValueData clientRelationshipType, final Long entityId, - final EnumOptionData guarantorType, final String firstname, final String lastname, final LocalDate dob, - final String addressLine1, final String addressLine2, final String city, final String state, final String zip, - final String country, final String mobileNumber, final String housePhoneNumber, final String comment, final String officeName, - final LocalDate joinedDate, final String externalId, final boolean status, - Collection guarantorFundingDetails, final List guarantorTypeOptions, - final Collection allowedClientRelationshipTypes, final Collection accountLinkingOptions) { - this.id = id; - this.loanId = loanId; - this.clientRelationshipType = clientRelationshipType; - this.guarantorType = guarantorType; - this.entityId = entityId; - this.firstname = firstname; - this.lastname = lastname; - this.dob = dob; - this.addressLine1 = addressLine1; - this.addressLine2 = addressLine2; - this.city = city; - this.state = state; - this.zip = zip; - this.country = country; - this.mobileNumber = mobileNumber; - this.housePhoneNumber = housePhoneNumber; - this.comment = comment; - this.officeName = officeName; - this.joinedDate = joinedDate; - this.externalId = externalId; - this.status = status; - this.guarantorFundingDetails = guarantorFundingDetails; - this.guarantorTypeOptions = guarantorTypeOptions; - this.allowedClientRelationshipTypes = allowedClientRelationshipTypes; - this.accountLinkingOptions = accountLinkingOptions; - } - - public boolean isExternalGuarantor() { - return GuarantorType.EXTERNAL.getValue().equals(this.guarantorType.getId().intValue()); - } - - public boolean isExistingClient() { - return GuarantorType.CUSTOMER.getValue().equals(this.guarantorType.getId().intValue()); - } - - public boolean isStaffMember() { - return GuarantorType.STAFF.getValue().equals(this.guarantorType.getId().intValue()); - } - - public boolean isExistingGroup() { - return GuarantorType.GROUP.getValue().equals(this.guarantorType.getId().intValue()); - } -} +/** + * 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.loanaccount.guarantor.data; + +import java.io.Serial; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; +import lombok.Getter; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.organisation.staff.data.StaffData; +import org.apache.fineract.portfolio.account.data.PortfolioAccountData; +import org.apache.fineract.portfolio.client.data.ClientData; +import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType; +import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorEnumerations; + +@Getter +public class GuarantorData implements IGuarantor { + + @Serial + private static final long serialVersionUID = 1L; + + private final Long id; + private final Long loanId; + private final CodeValueData clientRelationshipType; + private final EnumOptionData guarantorType; + + private final String firstname; + private final String lastname; + + /*** Fields for current customers/staff serving as guarantors **/ + private final Long entityId; + private final String externalId; + private final String officeName; + private final LocalDate joinedDate; + + /*** Fields for external persons serving as guarantors ***/ + + private final String addressLine1; + private final String addressLine2; + private final String city; + private final String state; + private final String zip; + private final String country; + private final String mobileNumber; + private final String housePhoneNumber; + private final String comment; + private final LocalDate dob; + private final Collection guarantorFundingDetails; + private final boolean status; + + // template + @SuppressWarnings("unused") + private final List guarantorTypeOptions; + private final Collection allowedClientRelationshipTypes; + private final Collection accountLinkingOptions; + + // import fields + private Integer guarantorTypeId; + private Integer clientRelationshipTypeId; + private Integer savingsId; + private BigDecimal amount; + private transient Long accountId; + private transient Integer rowIndex; + private String dateFormat; + private String locale; + + public static GuarantorData importInstance(Integer guarantorTypeId, Integer clientRelationshipTypeId, Long entityId, String firstname, + String lastname, String addressLine1, String addressLine2, String city, LocalDate dob, String zip, Integer savingsId, + BigDecimal amount, Integer rowIndex, Long accountId, String locale, String dateFormat) { + return new GuarantorData(guarantorTypeId, clientRelationshipTypeId, entityId, firstname, lastname, addressLine1, addressLine2, city, + dob, zip, savingsId, amount, rowIndex, accountId, locale, dateFormat); + } + + private GuarantorData(Integer guarantorTypeId, Integer clientRelationshipTypeId, Long entityId, String firstname, String lastname, + String addressLine1, String addressLine2, String city, LocalDate dob, String zip, Integer savingsId, BigDecimal amount, + Integer rowIndex, Long accountId, String locale, String dateFormat) { + this.rowIndex = rowIndex; + this.firstname = firstname; + this.lastname = lastname; + this.entityId = entityId; + this.addressLine1 = addressLine1; + this.addressLine2 = addressLine2; + this.city = city; + this.zip = zip; + this.dob = dob; + this.guarantorTypeId = guarantorTypeId; + this.clientRelationshipTypeId = clientRelationshipTypeId; + this.savingsId = savingsId; + this.amount = amount; + this.accountId = accountId; + this.dateFormat = dateFormat; + this.locale = locale; + this.clientRelationshipType = null; + this.guarantorType = null; + this.id = null; + this.loanId = null; + this.externalId = null; + this.officeName = null; + this.joinedDate = null; + this.state = null; + this.country = null; + this.mobileNumber = null; + this.housePhoneNumber = null; + this.comment = null; + this.guarantorFundingDetails = null; + this.status = false; + this.guarantorTypeOptions = null; + this.allowedClientRelationshipTypes = null; + this.accountLinkingOptions = null; + } + + public static GuarantorData template(final List guarantorTypeOptions, + final Collection allowedClientRelationshipTypes, Collection accountLinkingOptions) { + final Collection guarantorFundingDetails = null; + final boolean status = false; + return new GuarantorData(null, null, null, null, GuarantorEnumerations.guarantorType(GuarantorType.CUSTOMER), null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, status, guarantorFundingDetails, + guarantorTypeOptions, allowedClientRelationshipTypes, accountLinkingOptions); + } + + public static GuarantorData templateOnTop(final GuarantorData guarantorData, final List guarantorTypeOptions, + final Collection allowedClientRelationshipTypes, Collection accountLinkingOptions) { + return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId, + guarantorData.guarantorType, guarantorData.firstname, guarantorData.lastname, guarantorData.dob, guarantorData.addressLine1, + guarantorData.addressLine2, guarantorData.city, guarantorData.state, guarantorData.zip, guarantorData.country, + guarantorData.mobileNumber, guarantorData.housePhoneNumber, guarantorData.comment, guarantorData.officeName, + guarantorData.joinedDate, guarantorData.externalId, guarantorData.status, guarantorData.guarantorFundingDetails, + guarantorTypeOptions, allowedClientRelationshipTypes, accountLinkingOptions); + } + + public static GuarantorData mergeClientData(final ClientData clientData, final GuarantorData guarantorData) { + return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId, + guarantorData.guarantorType, clientData.getFirstname(), clientData.getLastname(), null, null, null, null, null, null, null, + null, null, null, clientData.getOfficeName(), clientData.getActivationDate(), + clientData.getExternalId() != null ? clientData.getExternalId().getValue() : null, + guarantorData.status, guarantorData.guarantorFundingDetails, null, guarantorData.allowedClientRelationshipTypes, + guarantorData.accountLinkingOptions); + } + + public static GuarantorData mergeStaffData(final StaffData staffData, final GuarantorData guarantorData) { + return new GuarantorData(guarantorData.id, guarantorData.loanId, guarantorData.clientRelationshipType, guarantorData.entityId, + guarantorData.guarantorType, staffData.getFirstname(), staffData.getLastname(), null, null, null, null, null, null, null, + null, null, null, staffData.getOfficeName(), null, null, guarantorData.status, guarantorData.guarantorFundingDetails, null, + guarantorData.allowedClientRelationshipTypes, guarantorData.accountLinkingOptions); + } + + public GuarantorData(final Long id, final Long loanId, final CodeValueData clientRelationshipType, final Long entityId, + final EnumOptionData guarantorType, final String firstname, final String lastname, final LocalDate dob, + final String addressLine1, final String addressLine2, final String city, final String state, final String zip, + final String country, final String mobileNumber, final String housePhoneNumber, final String comment, final String officeName, + final LocalDate joinedDate, final String externalId, final boolean status, + Collection guarantorFundingDetails, final List guarantorTypeOptions, + final Collection allowedClientRelationshipTypes, final Collection accountLinkingOptions) { + this.id = id; + this.loanId = loanId; + this.clientRelationshipType = clientRelationshipType; + this.guarantorType = guarantorType; + this.entityId = entityId; + this.firstname = firstname; + this.lastname = lastname; + this.dob = dob; + this.addressLine1 = addressLine1; + this.addressLine2 = addressLine2; + this.city = city; + this.state = state; + this.zip = zip; + this.country = country; + this.mobileNumber = mobileNumber; + this.housePhoneNumber = housePhoneNumber; + this.comment = comment; + this.officeName = officeName; + this.joinedDate = joinedDate; + this.externalId = externalId; + this.status = status; + this.guarantorFundingDetails = guarantorFundingDetails; + this.guarantorTypeOptions = guarantorTypeOptions; + this.allowedClientRelationshipTypes = allowedClientRelationshipTypes; + this.accountLinkingOptions = accountLinkingOptions; + } + + public boolean isExternalGuarantor() { + return GuarantorType.EXTERNAL.getValue().equals(this.guarantorType.getId().intValue()); + } + + public boolean isExistingClient() { + return GuarantorType.CUSTOMER.getValue().equals(this.guarantorType.getId().intValue()); + } + + public boolean isStaffMember() { + return GuarantorType.STAFF.getValue().equals(this.guarantorType.getId().intValue()); + } + + public boolean isExistingGroup() { + return GuarantorType.GROUP.getValue().equals(this.guarantorType.getId().intValue()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java index 82d532ad7..cb343376b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java @@ -1,611 +1,614 @@ -/** - * 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.loanaccount.guarantor.service; - -import jakarta.annotation.PostConstruct; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; -import org.apache.fineract.infrastructure.core.data.ApiParameterError; -import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; -import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; -import org.apache.fineract.infrastructure.event.business.BusinessEventListener; -import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.loan.LoanApprovedBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoApprovalBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoDisbursalBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPostBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWrittenOffPostBusinessEvent; -import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; -import org.apache.fineract.organisation.monetary.domain.MoneyHelper; -import org.apache.fineract.portfolio.account.PortfolioAccountType; -import org.apache.fineract.portfolio.account.data.AccountTransferDTO; -import org.apache.fineract.portfolio.account.domain.AccountTransferDetails; -import org.apache.fineract.portfolio.account.domain.AccountTransferType; -import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; -import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; -import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; -import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants; -import org.apache.fineract.portfolio.loanaccount.guarantor.domain.Guarantor; -import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingDetails; -import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingRepository; -import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransaction; -import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransactionRepository; -import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorRepository; -import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; -import org.apache.fineract.portfolio.loanproduct.domain.LoanProductGuaranteeDetails; -import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; -import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransaction; -import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository; -import org.apache.fineract.portfolio.savings.domain.SavingsAccount; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; -import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class GuarantorDomainServiceImpl implements GuarantorDomainService { - - private final GuarantorRepository guarantorRepository; - private final GuarantorFundingRepository guarantorFundingRepository; - private final GuarantorFundingTransactionRepository guarantorFundingTransactionRepository; - private final AccountTransfersWritePlatformService accountTransfersWritePlatformService; - private final BusinessEventNotifierService businessEventNotifierService; - private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; - private final Map releaseLoanIds = new HashMap<>(2); - private final SavingsAccountAssembler savingsAccountAssembler; - private final ConfigurationDomainService configurationDomainService; - private final ExternalIdFactory externalIdFactory; - private final LoanRepository loanRepository; - private final LoanTransactionRepository loanTransactionRepository; - - @PostConstruct - public void addListeners() { - businessEventNotifierService.addPostBusinessEventListener(LoanApprovedBusinessEvent.class, new ValidateOnBusinessEvent()); - businessEventNotifierService.addPostBusinessEventListener(LoanApprovedBusinessEvent.class, new HoldFundsOnBusinessEvent()); - businessEventNotifierService.addPostBusinessEventListener(LoanUndoApprovalBusinessEvent.class, new UndoAllFundTransactions()); - businessEventNotifierService.addPostBusinessEventListener(LoanUndoDisbursalBusinessEvent.class, - new ReverseAllFundsOnBusinessEvent()); - businessEventNotifierService.addPostBusinessEventListener(LoanAdjustTransactionBusinessEvent.class, - new AdjustFundsOnBusinessEvent()); - businessEventNotifierService.addPostBusinessEventListener(LoanTransactionMakeRepaymentPostBusinessEvent.class, - new ReleaseFundsOnBusinessEvent()); - businessEventNotifierService.addPostBusinessEventListener(LoanWrittenOffPostBusinessEvent.class, new ReleaseAllFunds()); - businessEventNotifierService.addPostBusinessEventListener(LoanUndoWrittenOffBusinessEvent.class, new ReverseFundsOnBusinessEvent()); - } - - @Override - public void validateGuarantorBusinessRules(Loan loan) { - LoanProduct loanProduct = loan.loanProduct(); - BigDecimal principal = loan.getPrincipal().getAmount(); - if (loanProduct.isHoldGuaranteeFunds()) { - LoanProductGuaranteeDetails guaranteeData = loanProduct.getLoanProductGuaranteeDetails(); - final List existGuarantorList = this.guarantorRepository.findByLoan(loan); - BigDecimal mandatoryAmount = principal.multiply(guaranteeData.getMandatoryGuarantee()).divide(BigDecimal.valueOf(100)); - BigDecimal minSelfAmount = principal.multiply(guaranteeData.getMinimumGuaranteeFromOwnFunds()).divide(BigDecimal.valueOf(100)); - BigDecimal minExtGuarantee = principal.multiply(guaranteeData.getMinimumGuaranteeFromGuarantor()) - .divide(BigDecimal.valueOf(100)); - - BigDecimal actualAmount = BigDecimal.ZERO; - BigDecimal actualSelfAmount = BigDecimal.ZERO; - BigDecimal actualExtGuarantee = BigDecimal.ZERO; - for (Guarantor guarantor : existGuarantorList) { - List fundingDetails = guarantor.getGuarantorFundDetails(); - for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { - if (guarantorFundingDetails.getStatus().isActive() || guarantorFundingDetails.getStatus().isWithdrawn() - || guarantorFundingDetails.getStatus().isCompleted()) { - if (guarantor.isSelfGuarantee()) { - actualSelfAmount = actualSelfAmount.add(guarantorFundingDetails.getAmount()) - .subtract(guarantorFundingDetails.getAmountTransfered()); - } else { - actualExtGuarantee = actualExtGuarantee.add(guarantorFundingDetails.getAmount()) - .subtract(guarantorFundingDetails.getAmountTransfered()); - } - } - } - } - - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor"); - if (actualSelfAmount.compareTo(minSelfAmount) < 0) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_SELF_GUARANTEE_ERROR, - minSelfAmount); - } - - if (actualExtGuarantee.compareTo(minExtGuarantee) < 0) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_EXTERNAL_GUARANTEE_ERROR, - minExtGuarantee); - } - actualAmount = actualAmount.add(actualExtGuarantee).add(actualSelfAmount); - if (actualAmount.compareTo(mandatoryAmount) < 0) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_MANDATORY_GUARANTEE_ERROR, - mandatoryAmount); - } - - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", - dataValidationErrors); - } - } - - } - - /** - * Method assigns a guarantor to loan and blocks the funds on guarantor's account - */ - @Override - public void assignGuarantor(final GuarantorFundingDetails guarantorFundingDetails, final LocalDate transactionDate) { - if (guarantorFundingDetails.getStatus().isActive()) { - SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); - savingsAccount.holdFunds(guarantorFundingDetails.getAmount()); - if (savingsAccount.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor"); - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR, - savingsAccount.getId()); - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", - dataValidationErrors); - } - DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.hold(savingsAccount, - guarantorFundingDetails.getAmount(), transactionDate); - GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, null, - onHoldTransaction); - guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); - this.depositAccountOnHoldTransactionRepository.saveAndFlush(onHoldTransaction); - } - } - - /** - * Method releases(withdraw) a guarantor from loan and unblocks the funds on guarantor's account - */ - @Override - public void releaseGuarantor(final GuarantorFundingDetails guarantorFundingDetails, final LocalDate transactionDate) { - BigDecimal amoutForWithdraw = guarantorFundingDetails.getAmountRemaining(); - if (amoutForWithdraw.compareTo(BigDecimal.ZERO) > 0 && guarantorFundingDetails.getStatus().isActive()) { - SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); - savingsAccount.releaseFunds(amoutForWithdraw); - DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, amoutForWithdraw, - transactionDate); - GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, null, - onHoldTransaction); - guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); - guarantorFundingDetails.releaseFunds(amoutForWithdraw); - guarantorFundingDetails.withdrawFunds(amoutForWithdraw); - guarantorFundingDetails.getLoanAccount().updateGuaranteeAmount(amoutForWithdraw.negate()); - this.depositAccountOnHoldTransactionRepository.saveAndFlush(onHoldTransaction); - this.guarantorFundingRepository.saveAndFlush(guarantorFundingDetails); - } - } - - /** - * Method is to recover funds from guarantor's in case loan is unpaid. (Transfers guarantee amount from guarantor's - * account to loan account and releases guarantor) - */ - @Override - public void transferFundsFromGuarantor(final Loan loan) { - if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) <= 0) { - return; - } - final List existGuarantorList = this.guarantorRepository.findByLoan(loan); - final boolean isRegularTransaction = true; - final boolean isExceptionForBalanceCheck = true; - LocalDate transactionDate = DateUtils.getBusinessLocalDate(); - PortfolioAccountType fromAccountType = PortfolioAccountType.SAVINGS; - PortfolioAccountType toAccountType = PortfolioAccountType.LOAN; - final Long toAccountId = loan.getId(); - final String description = "Payment from guarantor savings"; - final Locale locale = null; - final DateTimeFormatter fmt = null; - final PaymentDetail paymentDetail = null; - final Integer fromTransferType = null; - final Integer toTransferType = null; - final Long chargeId = null; - final Integer loanInstallmentNumber = null; - final Integer transferType = AccountTransferType.LOAN_REPAYMENT.getValue(); - final AccountTransferDetails accountTransferDetails = null; - final String noteText = null; - - Long loanId = loan.getId(); - - for (Guarantor guarantor : existGuarantorList) { - final List fundingDetails = guarantor.getGuarantorFundDetails(); - for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { - Loan freshLoan = loanRepository.findById(loanId).orElseThrow(); - if (guarantorFundingDetails.getStatus().isActive()) { - final SavingsAccount fromSavingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); - final Long fromAccountId = fromSavingsAccount.getId(); - releaseLoanIds.put(loanId, guarantorFundingDetails.getId()); - try { - BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining(); - if (freshLoan.getGuaranteeAmount().compareTo(freshLoan.getPrincipal().getAmount()) > 0) { - remainingAmount = remainingAmount.multiply(freshLoan.getPrincipal().getAmount()) - .divide(freshLoan.getGuaranteeAmount(), MoneyHelper.getRoundingMode()); - } - ExternalId externalId = externalIdFactory.create(); - AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType, - toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType, - toTransferType, chargeId, loanInstallmentNumber, transferType, accountTransferDetails, noteText, externalId, - null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck); - transferAmount(accountTransferDTO); - } finally { - releaseLoanIds.remove(loanId); - } - } - } - } - - } - - /** - * @param accountTransferDTO - */ - private void transferAmount(final AccountTransferDTO accountTransferDTO) { - try { - this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); - } catch (final InsufficientAccountBalanceException e) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor"); - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR, - accountTransferDTO.getFromAccountId(), accountTransferDTO.getToAccountId()); - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", - dataValidationErrors, e); - - } - } - - /** - * Method reverses all blocked fund(both hold and release) transactions. example: reverses all transactions on undo - * approval of loan account. - * - */ - private void reverseAllFundTransaction(final Loan loan) { - - if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) > 0) { - final List existGuarantorList = this.guarantorRepository.findByLoan(loan); - List guarantorFundingDetailList = new ArrayList<>(); - for (Guarantor guarantor : existGuarantorList) { - final List fundingDetails = guarantor.getGuarantorFundDetails(); - for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { - guarantorFundingDetails.undoAllTransactions(); - guarantorFundingDetailList.add(guarantorFundingDetails); - } - } - - if (!guarantorFundingDetailList.isEmpty()) { - loan.setGuaranteeAmount(null); - this.guarantorFundingRepository.saveAll(guarantorFundingDetailList); - } - } - } - - /** - * Method holds all guarantor's guarantee amount for a loan account. example: hold funds on approval of loan - * account. - * - */ - private void holdGuarantorFunds(final Loan loan) { - if (loan.loanProduct().isHoldGuaranteeFunds()) { - final List existGuarantorList = this.guarantorRepository.findByLoan(loan); - List guarantorFundingDetailList = new ArrayList<>(); - List onHoldTransactions = new ArrayList<>(); - BigDecimal totalGuarantee = BigDecimal.ZERO; - List insufficientBalanceIds = new ArrayList<>(); - for (Guarantor guarantor : existGuarantorList) { - final List fundingDetails = guarantor.getGuarantorFundDetails(); - for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { - if (guarantorFundingDetails.getStatus().isActive()) { - final SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); - if (loan.isApproved() && !loan.isDisbursed()) { - final List transactions = new ArrayList<>(); - for (final SavingsAccountTransaction transaction : savingsAccount.getTransactions()) { - if (!DateUtils.isAfter(transaction.getTransactionDate(), loan.getApprovedOnDate())) { - transactions.add(transaction); - } - } - this.savingsAccountAssembler.setHelpers(savingsAccount); - savingsAccount.updateSavingsAccountSummary(transactions); - } - savingsAccount.holdFunds(guarantorFundingDetails.getAmount()); - totalGuarantee = totalGuarantee.add(guarantorFundingDetails.getAmount()); - DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.hold(savingsAccount, - guarantorFundingDetails.getAmount(), loan.getApprovedOnDate()); - onHoldTransactions.add(onHoldTransaction); - GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, - null, onHoldTransaction); - guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); - guarantorFundingDetailList.add(guarantorFundingDetails); - if (savingsAccount.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) { - insufficientBalanceIds.add(savingsAccount.getId()); - } - savingsAccount.updateSavingsAccountSummary(savingsAccount.getTransactions()); - } - } - } - if (!insufficientBalanceIds.isEmpty()) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor"); - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR, - insufficientBalanceIds); - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", - dataValidationErrors); - - } - loan.setGuaranteeAmount(totalGuarantee); - if (!guarantorFundingDetailList.isEmpty()) { - this.depositAccountOnHoldTransactionRepository.saveAll(onHoldTransactions); - this.guarantorFundingRepository.saveAll(guarantorFundingDetailList); - } - } - } - - /** - * Method releases all guarantor's guarantee amount(first external guarantee and then self guarantee) for a loan - * account in the portion of guarantee percentage on a paid principal. example: releases funds on repayments of loan - * account. - * - */ - private void releaseGuarantorFunds(final LoanTransaction loanTransaction) { - final Loan loan = loanTransaction.getLoan(); - if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) > 0) { - final List existGuarantorList = this.guarantorRepository.findByLoan(loan); - List externalGuarantorList = new ArrayList<>(); - List selfGuarantorList = new ArrayList<>(); - BigDecimal selfGuarantee = BigDecimal.ZERO; - BigDecimal guarantorGuarantee = BigDecimal.ZERO; - for (Guarantor guarantor : existGuarantorList) { - final List fundingDetails = guarantor.getGuarantorFundDetails(); - for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { - if (guarantorFundingDetails.getStatus().isActive()) { - if (guarantor.isSelfGuarantee()) { - selfGuarantorList.add(guarantorFundingDetails); - selfGuarantee = selfGuarantee.add(guarantorFundingDetails.getAmountRemaining()); - } else if (guarantor.isExistingCustomer() || guarantor.isExistingGroup()) { - externalGuarantorList.add(guarantorFundingDetails); - guarantorGuarantee = guarantorGuarantee.add(guarantorFundingDetails.getAmountRemaining()); - } - } - } - } - - BigDecimal amountForRelease = loanTransaction.getPrincipalPortion(); - BigDecimal totalGuaranteeAmount = loan.getGuaranteeAmount(); - BigDecimal principal = loan.getPrincipal().getAmount(); - if ((amountForRelease != null) && (totalGuaranteeAmount != null)) { - amountForRelease = amountForRelease.multiply(totalGuaranteeAmount).divide(principal, MoneyHelper.getRoundingMode()); - List accountOnHoldTransactions = new ArrayList<>(); - - BigDecimal amountLeft = calculateAndRelaseGuarantorFunds(externalGuarantorList, guarantorGuarantee, amountForRelease, - loanTransaction, accountOnHoldTransactions); - - if (amountLeft.compareTo(BigDecimal.ZERO) > 0) { - calculateAndRelaseGuarantorFunds(selfGuarantorList, selfGuarantee, amountLeft, loanTransaction, - accountOnHoldTransactions); - externalGuarantorList.addAll(selfGuarantorList); - } - - if (!externalGuarantorList.isEmpty()) { - this.depositAccountOnHoldTransactionRepository.saveAll(accountOnHoldTransactions); - this.guarantorFundingRepository.saveAll(externalGuarantorList); - } - } - } - - } - - /** - * Method releases all guarantor's guarantee amount. example: releases funds on write-off of a loan account. - * - */ - private void releaseAllGuarantors(final LoanTransaction loanTransaction) { - Loan loan = loanTransaction.getLoan(); - if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) > 0) { - final List existGuarantorList = this.guarantorRepository.findByLoan(loan); - List saveGuarantorFundingDetails = new ArrayList<>(); - List onHoldTransactions = new ArrayList<>(); - for (Guarantor guarantor : existGuarantorList) { - final List fundingDetails = guarantor.getGuarantorFundDetails(); - for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { - BigDecimal amoutForRelease = guarantorFundingDetails.getAmountRemaining(); - if (amoutForRelease.compareTo(BigDecimal.ZERO) > 0 && guarantorFundingDetails.getStatus().isActive()) { - SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); - savingsAccount.releaseFunds(amoutForRelease); - DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, - amoutForRelease, loanTransaction.getTransactionDate()); - onHoldTransactions.add(onHoldTransaction); - GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, - loanTransaction, onHoldTransaction); - guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); - guarantorFundingDetails.releaseFunds(amoutForRelease); - saveGuarantorFundingDetails.add(guarantorFundingDetails); - - } - } - - } - - if (!saveGuarantorFundingDetails.isEmpty()) { - this.depositAccountOnHoldTransactionRepository.saveAll(onHoldTransactions); - this.guarantorFundingRepository.saveAll(saveGuarantorFundingDetails); - } - } - } - - /** - * Method releases guarantor's guarantee amount on transferring guarantee amount to loan account. example: on - * recovery of guarantee funds from guarantor's. - */ - private void completeGuarantorFund(final LoanTransaction loanTransaction) { - Loan loan = loanTransaction.getLoan(); - GuarantorFundingDetails guarantorFundingDetails = this.guarantorFundingRepository.findById(releaseLoanIds.get(loan.getId())) - .orElse(null); - if (guarantorFundingDetails != null) { - BigDecimal amountForRelease = loanTransaction.getAmount(loan.getCurrency()).getAmount(); - BigDecimal guarantorGuarantee = amountForRelease; - List guarantorList = Arrays.asList(guarantorFundingDetails); - final List accountOnHoldTransactions = new ArrayList<>(); - calculateAndRelaseGuarantorFunds(guarantorList, guarantorGuarantee, amountForRelease, loanTransaction, - accountOnHoldTransactions); - this.depositAccountOnHoldTransactionRepository.saveAll(accountOnHoldTransactions); - this.guarantorFundingRepository.saveAndFlush(guarantorFundingDetails); - } - } - - private BigDecimal calculateAndRelaseGuarantorFunds(List guarantorList, BigDecimal totalGuaranteeAmount, - BigDecimal amountForRelease, LoanTransaction loanTransaction, - final List accountOnHoldTransactions) { - BigDecimal amountLeft = amountForRelease; - for (GuarantorFundingDetails fundingDetails : guarantorList) { - BigDecimal guarantorAmount = amountForRelease.multiply(fundingDetails.getAmountRemaining()).divide(totalGuaranteeAmount, - MoneyHelper.getRoundingMode()); - if (fundingDetails.getAmountRemaining().compareTo(guarantorAmount) <= 0) { - guarantorAmount = fundingDetails.getAmountRemaining(); - } - fundingDetails.releaseFunds(guarantorAmount); - SavingsAccount savingsAccount = fundingDetails.getLinkedSavingsAccount(); - savingsAccount.releaseFunds(guarantorAmount); - DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, guarantorAmount, - loanTransaction.getTransactionDate()); - accountOnHoldTransactions.add(onHoldTransaction); - GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(fundingDetails, loanTransaction, - onHoldTransaction); - fundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); - amountLeft = amountLeft.subtract(guarantorAmount); - } - return amountLeft; - } - - /** - * Method reverses the fund release transactions in case of loan transaction reversed - */ - private void reverseTransaction(final List loanTransactionIds) { - - List fundingTransactions = this.guarantorFundingTransactionRepository - .fetchGuarantorFundingTransactions(loanTransactionIds); - for (GuarantorFundingTransaction fundingTransaction : fundingTransactions) { - fundingTransaction.reverseTransaction(); - } - if (!fundingTransactions.isEmpty()) { - this.guarantorFundingTransactionRepository.saveAll(fundingTransactions); - } - } - - private final class ValidateOnBusinessEvent implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanApprovedBusinessEvent event) { - Loan loan = event.get(); - validateGuarantorBusinessRules(loan); - } - } - - private final class HoldFundsOnBusinessEvent implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanApprovedBusinessEvent event) { - Loan loan = event.get(); - holdGuarantorFunds(loan); - } - } - - private final class ReleaseFundsOnBusinessEvent implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanTransactionMakeRepaymentPostBusinessEvent event) { - LoanTransaction loanTransaction = event.get(); - if (releaseLoanIds.containsKey(loanTransaction.getLoan().getId())) { - completeGuarantorFund(loanTransaction); - } else { - releaseGuarantorFunds(loanTransaction); - } - } - } - - private final class ReverseFundsOnBusinessEvent implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanUndoWrittenOffBusinessEvent event) { - LoanTransaction loanTransaction = event.get(); - List reversedTransactions = new ArrayList<>(); - reversedTransactions.add(loanTransaction.getId()); - reverseTransaction(reversedTransactions); - } - } - - private final class AdjustFundsOnBusinessEvent implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanAdjustTransactionBusinessEvent event) { - LoanAdjustTransactionBusinessEvent.Data eventData = event.get(); - - LoanTransaction loanTransaction = eventData.getTransactionToAdjust(); - List reersedTransactions = new ArrayList<>(1); - reersedTransactions.add(loanTransaction.getId()); - reverseTransaction(reersedTransactions); - - LoanTransaction newTransaction = event.get().getNewTransactionDetail(); - if (newTransaction != null) { - releaseGuarantorFunds(newTransaction); - } - } - } - - private final class ReverseAllFundsOnBusinessEvent implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanUndoDisbursalBusinessEvent event) { - Loan loan = event.get(); - List reversedTransactions = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan)); - reverseTransaction(reversedTransactions); - } - } - - private final class UndoAllFundTransactions implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanUndoApprovalBusinessEvent event) { - Loan loan = event.get(); - reverseAllFundTransaction(loan); - } - } - - private final class ReleaseAllFunds implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanWrittenOffPostBusinessEvent event) { - LoanTransaction loanTransaction = event.get(); - releaseAllGuarantors(loanTransaction); - } - } - -} +/** + * 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.loanaccount.guarantor.service; + +import jakarta.annotation.PostConstruct; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.event.business.BusinessEventListener; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanApprovedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoApprovalBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoDisbursalBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPostBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWrittenOffPostBusinessEvent; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.account.PortfolioAccountType; +import org.apache.fineract.portfolio.account.data.AccountTransferDTO; +import org.apache.fineract.portfolio.account.domain.AccountTransferDetails; +import org.apache.fineract.portfolio.account.domain.AccountTransferType; +import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants; +import org.apache.fineract.portfolio.loanaccount.guarantor.domain.Guarantor; +import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingDetails; +import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingRepository; +import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransaction; +import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorRepository; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductGuaranteeDetails; +import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; +import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransaction; +import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; +import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class GuarantorDomainServiceImpl implements GuarantorDomainService { + + private final GuarantorRepository guarantorRepository; + private final GuarantorFundingRepository guarantorFundingRepository; + private final GuarantorFundingTransactionRepository guarantorFundingTransactionRepository; + private final AccountTransfersWritePlatformService accountTransfersWritePlatformService; + private final BusinessEventNotifierService businessEventNotifierService; + private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; + private final Map releaseLoanIds = new HashMap<>(2); + private final SavingsAccountAssembler savingsAccountAssembler; + private final ConfigurationDomainService configurationDomainService; + private final ExternalIdFactory externalIdFactory; + private final LoanRepository loanRepository; + private final LoanTransactionRepository loanTransactionRepository; + + @PostConstruct + public void addListeners() { + businessEventNotifierService.addPostBusinessEventListener(LoanApprovedBusinessEvent.class, new ValidateOnBusinessEvent()); + businessEventNotifierService.addPostBusinessEventListener(LoanApprovedBusinessEvent.class, new HoldFundsOnBusinessEvent()); + businessEventNotifierService.addPostBusinessEventListener(LoanUndoApprovalBusinessEvent.class, new UndoAllFundTransactions()); + businessEventNotifierService.addPostBusinessEventListener(LoanUndoDisbursalBusinessEvent.class, + new ReverseAllFundsOnBusinessEvent()); + businessEventNotifierService.addPostBusinessEventListener(LoanAdjustTransactionBusinessEvent.class, + new AdjustFundsOnBusinessEvent()); + businessEventNotifierService.addPostBusinessEventListener(LoanTransactionMakeRepaymentPostBusinessEvent.class, + new ReleaseFundsOnBusinessEvent()); + businessEventNotifierService.addPostBusinessEventListener(LoanWrittenOffPostBusinessEvent.class, new ReleaseAllFunds()); + businessEventNotifierService.addPostBusinessEventListener(LoanUndoWrittenOffBusinessEvent.class, new ReverseFundsOnBusinessEvent()); + } + + @Override + public void validateGuarantorBusinessRules(Loan loan) { + LoanProduct loanProduct = loan.loanProduct(); + BigDecimal principal = loan.getPrincipal().getAmount(); + if (loanProduct.isHoldGuaranteeFunds()) { + LoanProductGuaranteeDetails guaranteeData = loanProduct.getLoanProductGuaranteeDetails(); + if (guaranteeData == null) { + return; + } + final List existGuarantorList = this.guarantorRepository.findByLoan(loan); + BigDecimal mandatoryAmount = principal.multiply(guaranteeData.getMandatoryGuarantee()).divide(BigDecimal.valueOf(100)); + BigDecimal minSelfAmount = principal.multiply(guaranteeData.getMinimumGuaranteeFromOwnFunds()).divide(BigDecimal.valueOf(100)); + BigDecimal minExtGuarantee = principal.multiply(guaranteeData.getMinimumGuaranteeFromGuarantor()) + .divide(BigDecimal.valueOf(100)); + + BigDecimal actualAmount = BigDecimal.ZERO; + BigDecimal actualSelfAmount = BigDecimal.ZERO; + BigDecimal actualExtGuarantee = BigDecimal.ZERO; + for (Guarantor guarantor : existGuarantorList) { + List fundingDetails = guarantor.getGuarantorFundDetails(); + for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { + if (guarantorFundingDetails.getStatus().isActive() || guarantorFundingDetails.getStatus().isWithdrawn() + || guarantorFundingDetails.getStatus().isCompleted()) { + if (guarantor.isSelfGuarantee()) { + actualSelfAmount = actualSelfAmount.add(guarantorFundingDetails.getAmount()) + .subtract(guarantorFundingDetails.getAmountTransfered()); + } else { + actualExtGuarantee = actualExtGuarantee.add(guarantorFundingDetails.getAmount()) + .subtract(guarantorFundingDetails.getAmountTransfered()); + } + } + } + } + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor"); + if (actualSelfAmount.compareTo(minSelfAmount) < 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_SELF_GUARANTEE_ERROR, + minSelfAmount); + } + + if (actualExtGuarantee.compareTo(minExtGuarantee) < 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_EXTERNAL_GUARANTEE_ERROR, + minExtGuarantee); + } + actualAmount = actualAmount.add(actualExtGuarantee).add(actualSelfAmount); + if (actualAmount.compareTo(mandatoryAmount) < 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_MANDATORY_GUARANTEE_ERROR, + mandatoryAmount); + } + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors); + } + } + + } + + /** + * Method assigns a guarantor to loan and blocks the funds on guarantor's account + */ + @Override + public void assignGuarantor(final GuarantorFundingDetails guarantorFundingDetails, final LocalDate transactionDate) { + if (guarantorFundingDetails.getStatus().isActive()) { + SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); + savingsAccount.holdFunds(guarantorFundingDetails.getAmount()); + if (savingsAccount.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor"); + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR, + savingsAccount.getId()); + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors); + } + DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.hold(savingsAccount, + guarantorFundingDetails.getAmount(), transactionDate); + GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, null, + onHoldTransaction); + guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); + this.depositAccountOnHoldTransactionRepository.saveAndFlush(onHoldTransaction); + } + } + + /** + * Method releases(withdraw) a guarantor from loan and unblocks the funds on guarantor's account + */ + @Override + public void releaseGuarantor(final GuarantorFundingDetails guarantorFundingDetails, final LocalDate transactionDate) { + BigDecimal amoutForWithdraw = guarantorFundingDetails.getAmountRemaining(); + if (amoutForWithdraw.compareTo(BigDecimal.ZERO) > 0 && guarantorFundingDetails.getStatus().isActive()) { + SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); + savingsAccount.releaseFunds(amoutForWithdraw); + DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, amoutForWithdraw, + transactionDate); + GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, null, + onHoldTransaction); + guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); + guarantorFundingDetails.releaseFunds(amoutForWithdraw); + guarantorFundingDetails.withdrawFunds(amoutForWithdraw); + guarantorFundingDetails.getLoanAccount().updateGuaranteeAmount(amoutForWithdraw.negate()); + this.depositAccountOnHoldTransactionRepository.saveAndFlush(onHoldTransaction); + this.guarantorFundingRepository.saveAndFlush(guarantorFundingDetails); + } + } + + /** + * Method is to recover funds from guarantor's in case loan is unpaid. (Transfers guarantee amount from guarantor's + * account to loan account and releases guarantor) + */ + @Override + public void transferFundsFromGuarantor(final Loan loan) { + if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) <= 0) { + return; + } + final List existGuarantorList = this.guarantorRepository.findByLoan(loan); + final boolean isRegularTransaction = true; + final boolean isExceptionForBalanceCheck = true; + LocalDate transactionDate = DateUtils.getBusinessLocalDate(); + PortfolioAccountType fromAccountType = PortfolioAccountType.SAVINGS; + PortfolioAccountType toAccountType = PortfolioAccountType.LOAN; + final Long toAccountId = loan.getId(); + final String description = "Payment from guarantor savings"; + final Locale locale = null; + final DateTimeFormatter fmt = null; + final PaymentDetail paymentDetail = null; + final Integer fromTransferType = null; + final Integer toTransferType = null; + final Long chargeId = null; + final Integer loanInstallmentNumber = null; + final Integer transferType = AccountTransferType.LOAN_REPAYMENT.getValue(); + final AccountTransferDetails accountTransferDetails = null; + final String noteText = null; + + Long loanId = loan.getId(); + + for (Guarantor guarantor : existGuarantorList) { + final List fundingDetails = guarantor.getGuarantorFundDetails(); + for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { + Loan freshLoan = loanRepository.findById(loanId).orElseThrow(); + if (guarantorFundingDetails.getStatus().isActive()) { + final SavingsAccount fromSavingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); + final Long fromAccountId = fromSavingsAccount.getId(); + releaseLoanIds.put(loanId, guarantorFundingDetails.getId()); + try { + BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining(); + if (freshLoan.getGuaranteeAmount().compareTo(freshLoan.getPrincipal().getAmount()) > 0) { + remainingAmount = remainingAmount.multiply(freshLoan.getPrincipal().getAmount()) + .divide(freshLoan.getGuaranteeAmount(), MoneyHelper.getRoundingMode()); + } + ExternalId externalId = externalIdFactory.create(); + AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType, + toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType, + toTransferType, chargeId, loanInstallmentNumber, transferType, accountTransferDetails, noteText, externalId, + null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck); + transferAmount(accountTransferDTO); + } finally { + releaseLoanIds.remove(loanId); + } + } + } + } + + } + + /** + * @param accountTransferDTO + */ + private void transferAmount(final AccountTransferDTO accountTransferDTO) { + try { + this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); + } catch (final InsufficientAccountBalanceException e) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor"); + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR, + accountTransferDTO.getFromAccountId(), accountTransferDTO.getToAccountId()); + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors, e); + + } + } + + /** + * Method reverses all blocked fund(both hold and release) transactions. example: reverses all transactions on undo + * approval of loan account. + * + */ + private void reverseAllFundTransaction(final Loan loan) { + + if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) > 0) { + final List existGuarantorList = this.guarantorRepository.findByLoan(loan); + List guarantorFundingDetailList = new ArrayList<>(); + for (Guarantor guarantor : existGuarantorList) { + final List fundingDetails = guarantor.getGuarantorFundDetails(); + for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { + guarantorFundingDetails.undoAllTransactions(); + guarantorFundingDetailList.add(guarantorFundingDetails); + } + } + + if (!guarantorFundingDetailList.isEmpty()) { + loan.setGuaranteeAmount(null); + this.guarantorFundingRepository.saveAll(guarantorFundingDetailList); + } + } + } + + /** + * Method holds all guarantor's guarantee amount for a loan account. example: hold funds on approval of loan + * account. + * + */ + private void holdGuarantorFunds(final Loan loan) { + if (loan.loanProduct().isHoldGuaranteeFunds()) { + final List existGuarantorList = this.guarantorRepository.findByLoan(loan); + List guarantorFundingDetailList = new ArrayList<>(); + List onHoldTransactions = new ArrayList<>(); + BigDecimal totalGuarantee = BigDecimal.ZERO; + List insufficientBalanceIds = new ArrayList<>(); + for (Guarantor guarantor : existGuarantorList) { + final List fundingDetails = guarantor.getGuarantorFundDetails(); + for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { + if (guarantorFundingDetails.getStatus().isActive()) { + final SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); + if (loan.isApproved() && !loan.isDisbursed()) { + final List transactions = new ArrayList<>(); + for (final SavingsAccountTransaction transaction : savingsAccount.getTransactions()) { + if (!DateUtils.isAfter(transaction.getTransactionDate(), loan.getApprovedOnDate())) { + transactions.add(transaction); + } + } + this.savingsAccountAssembler.setHelpers(savingsAccount); + savingsAccount.updateSavingsAccountSummary(transactions); + } + savingsAccount.holdFunds(guarantorFundingDetails.getAmount()); + totalGuarantee = totalGuarantee.add(guarantorFundingDetails.getAmount()); + DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.hold(savingsAccount, + guarantorFundingDetails.getAmount(), loan.getApprovedOnDate()); + onHoldTransactions.add(onHoldTransaction); + GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, + null, onHoldTransaction); + guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); + guarantorFundingDetailList.add(guarantorFundingDetails); + if (savingsAccount.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) { + insufficientBalanceIds.add(savingsAccount.getId()); + } + savingsAccount.updateSavingsAccountSummary(savingsAccount.getTransactions()); + } + } + } + if (!insufficientBalanceIds.isEmpty()) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor"); + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR, + insufficientBalanceIds); + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors); + + } + loan.setGuaranteeAmount(totalGuarantee); + if (!guarantorFundingDetailList.isEmpty()) { + this.depositAccountOnHoldTransactionRepository.saveAll(onHoldTransactions); + this.guarantorFundingRepository.saveAll(guarantorFundingDetailList); + } + } + } + + /** + * Method releases all guarantor's guarantee amount(first external guarantee and then self guarantee) for a loan + * account in the portion of guarantee percentage on a paid principal. example: releases funds on repayments of loan + * account. + * + */ + private void releaseGuarantorFunds(final LoanTransaction loanTransaction) { + final Loan loan = loanTransaction.getLoan(); + if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) > 0) { + final List existGuarantorList = this.guarantorRepository.findByLoan(loan); + List externalGuarantorList = new ArrayList<>(); + List selfGuarantorList = new ArrayList<>(); + BigDecimal selfGuarantee = BigDecimal.ZERO; + BigDecimal guarantorGuarantee = BigDecimal.ZERO; + for (Guarantor guarantor : existGuarantorList) { + final List fundingDetails = guarantor.getGuarantorFundDetails(); + for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { + if (guarantorFundingDetails.getStatus().isActive()) { + if (guarantor.isSelfGuarantee()) { + selfGuarantorList.add(guarantorFundingDetails); + selfGuarantee = selfGuarantee.add(guarantorFundingDetails.getAmountRemaining()); + } else if (guarantor.isExistingCustomer() || guarantor.isExistingGroup()) { + externalGuarantorList.add(guarantorFundingDetails); + guarantorGuarantee = guarantorGuarantee.add(guarantorFundingDetails.getAmountRemaining()); + } + } + } + } + + BigDecimal amountForRelease = loanTransaction.getPrincipalPortion(); + BigDecimal totalGuaranteeAmount = loan.getGuaranteeAmount(); + BigDecimal principal = loan.getPrincipal().getAmount(); + if ((amountForRelease != null) && (totalGuaranteeAmount != null)) { + amountForRelease = amountForRelease.multiply(totalGuaranteeAmount).divide(principal, MoneyHelper.getRoundingMode()); + List accountOnHoldTransactions = new ArrayList<>(); + + BigDecimal amountLeft = calculateAndRelaseGuarantorFunds(externalGuarantorList, guarantorGuarantee, amountForRelease, + loanTransaction, accountOnHoldTransactions); + + if (amountLeft.compareTo(BigDecimal.ZERO) > 0) { + calculateAndRelaseGuarantorFunds(selfGuarantorList, selfGuarantee, amountLeft, loanTransaction, + accountOnHoldTransactions); + externalGuarantorList.addAll(selfGuarantorList); + } + + if (!externalGuarantorList.isEmpty()) { + this.depositAccountOnHoldTransactionRepository.saveAll(accountOnHoldTransactions); + this.guarantorFundingRepository.saveAll(externalGuarantorList); + } + } + } + + } + + /** + * Method releases all guarantor's guarantee amount. example: releases funds on write-off of a loan account. + * + */ + private void releaseAllGuarantors(final LoanTransaction loanTransaction) { + Loan loan = loanTransaction.getLoan(); + if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) > 0) { + final List existGuarantorList = this.guarantorRepository.findByLoan(loan); + List saveGuarantorFundingDetails = new ArrayList<>(); + List onHoldTransactions = new ArrayList<>(); + for (Guarantor guarantor : existGuarantorList) { + final List fundingDetails = guarantor.getGuarantorFundDetails(); + for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) { + BigDecimal amoutForRelease = guarantorFundingDetails.getAmountRemaining(); + if (amoutForRelease.compareTo(BigDecimal.ZERO) > 0 && guarantorFundingDetails.getStatus().isActive()) { + SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount(); + savingsAccount.releaseFunds(amoutForRelease); + DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, + amoutForRelease, loanTransaction.getTransactionDate()); + onHoldTransactions.add(onHoldTransaction); + GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, + loanTransaction, onHoldTransaction); + guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); + guarantorFundingDetails.releaseFunds(amoutForRelease); + saveGuarantorFundingDetails.add(guarantorFundingDetails); + + } + } + + } + + if (!saveGuarantorFundingDetails.isEmpty()) { + this.depositAccountOnHoldTransactionRepository.saveAll(onHoldTransactions); + this.guarantorFundingRepository.saveAll(saveGuarantorFundingDetails); + } + } + } + + /** + * Method releases guarantor's guarantee amount on transferring guarantee amount to loan account. example: on + * recovery of guarantee funds from guarantor's. + */ + private void completeGuarantorFund(final LoanTransaction loanTransaction) { + Loan loan = loanTransaction.getLoan(); + GuarantorFundingDetails guarantorFundingDetails = this.guarantorFundingRepository.findById(releaseLoanIds.get(loan.getId())) + .orElse(null); + if (guarantorFundingDetails != null) { + BigDecimal amountForRelease = loanTransaction.getAmount(loan.getCurrency()).getAmount(); + BigDecimal guarantorGuarantee = amountForRelease; + List guarantorList = Arrays.asList(guarantorFundingDetails); + final List accountOnHoldTransactions = new ArrayList<>(); + calculateAndRelaseGuarantorFunds(guarantorList, guarantorGuarantee, amountForRelease, loanTransaction, + accountOnHoldTransactions); + this.depositAccountOnHoldTransactionRepository.saveAll(accountOnHoldTransactions); + this.guarantorFundingRepository.saveAndFlush(guarantorFundingDetails); + } + } + + private BigDecimal calculateAndRelaseGuarantorFunds(List guarantorList, BigDecimal totalGuaranteeAmount, + BigDecimal amountForRelease, LoanTransaction loanTransaction, + final List accountOnHoldTransactions) { + BigDecimal amountLeft = amountForRelease; + for (GuarantorFundingDetails fundingDetails : guarantorList) { + BigDecimal guarantorAmount = amountForRelease.multiply(fundingDetails.getAmountRemaining()).divide(totalGuaranteeAmount, + MoneyHelper.getRoundingMode()); + if (fundingDetails.getAmountRemaining().compareTo(guarantorAmount) <= 0) { + guarantorAmount = fundingDetails.getAmountRemaining(); + } + fundingDetails.releaseFunds(guarantorAmount); + SavingsAccount savingsAccount = fundingDetails.getLinkedSavingsAccount(); + savingsAccount.releaseFunds(guarantorAmount); + DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, guarantorAmount, + loanTransaction.getTransactionDate()); + accountOnHoldTransactions.add(onHoldTransaction); + GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(fundingDetails, loanTransaction, + onHoldTransaction); + fundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction); + amountLeft = amountLeft.subtract(guarantorAmount); + } + return amountLeft; + } + + /** + * Method reverses the fund release transactions in case of loan transaction reversed + */ + private void reverseTransaction(final List loanTransactionIds) { + + List fundingTransactions = this.guarantorFundingTransactionRepository + .fetchGuarantorFundingTransactions(loanTransactionIds); + for (GuarantorFundingTransaction fundingTransaction : fundingTransactions) { + fundingTransaction.reverseTransaction(); + } + if (!fundingTransactions.isEmpty()) { + this.guarantorFundingTransactionRepository.saveAll(fundingTransactions); + } + } + + private final class ValidateOnBusinessEvent implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanApprovedBusinessEvent event) { + Loan loan = event.get(); + validateGuarantorBusinessRules(loan); + } + } + + private final class HoldFundsOnBusinessEvent implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanApprovedBusinessEvent event) { + Loan loan = event.get(); + holdGuarantorFunds(loan); + } + } + + private final class ReleaseFundsOnBusinessEvent implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanTransactionMakeRepaymentPostBusinessEvent event) { + LoanTransaction loanTransaction = event.get(); + if (releaseLoanIds.containsKey(loanTransaction.getLoan().getId())) { + completeGuarantorFund(loanTransaction); + } else { + releaseGuarantorFunds(loanTransaction); + } + } + } + + private final class ReverseFundsOnBusinessEvent implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanUndoWrittenOffBusinessEvent event) { + LoanTransaction loanTransaction = event.get(); + List reversedTransactions = new ArrayList<>(); + reversedTransactions.add(loanTransaction.getId()); + reverseTransaction(reversedTransactions); + } + } + + private final class AdjustFundsOnBusinessEvent implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanAdjustTransactionBusinessEvent event) { + LoanAdjustTransactionBusinessEvent.Data eventData = event.get(); + + LoanTransaction loanTransaction = eventData.getTransactionToAdjust(); + List reersedTransactions = new ArrayList<>(1); + reersedTransactions.add(loanTransaction.getId()); + reverseTransaction(reersedTransactions); + + LoanTransaction newTransaction = event.get().getNewTransactionDetail(); + if (newTransaction != null) { + releaseGuarantorFunds(newTransaction); + } + } + } + + private final class ReverseAllFundsOnBusinessEvent implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanUndoDisbursalBusinessEvent event) { + Loan loan = event.get(); + List reversedTransactions = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan)); + reverseTransaction(reversedTransactions); + } + } + + private final class UndoAllFundTransactions implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanUndoApprovalBusinessEvent event) { + Loan loan = event.get(); + reverseAllFundTransaction(loan); + } + } + + private final class ReleaseAllFunds implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanWrittenOffPostBusinessEvent event) { + LoanTransaction loanTransaction = event.get(); + releaseAllGuarantors(loanTransaction); + } + } + +}