From 2ba7e281087a01f687b86c0b48ac70a0dd9845bc Mon Sep 17 00:00:00 2001 From: Oleksii Novikov Date: Wed, 2 Apr 2025 11:13:34 +0300 Subject: [PATCH] FINERACT-2080: Extract DTO mappers from loan entity --- .../data/AccountingBridgeDataDTO.java | 49 ++ .../AccountingBridgeLoanTransactionDTO.java | 56 ++ .../loanaccount/data/LoanChargePaidByDTO.java | 37 ++ .../portfolio/loanaccount/domain/Loan.java | 584 +----------------- .../mapper/LoanAccountingBridgeMapper.java | 251 ++++++++ .../loanaccount/mapper/LoanMapper.java | 102 +++ .../mapper/LoanTermVariationsMapper.java | 148 +++++ .../service/LoanScheduleService.java | 4 +- .../LoanTransactionProcessingService.java | 6 +- .../service/AccountingProcessorHelper.java | 115 ++-- .../JournalEntryWritePlatformService.java | 3 +- ...WritePlatformServiceJpaRepositoryImpl.java | 13 +- .../AccountingJournalEntryConfiguration.java | 10 +- .../domain/LoanAccountDomainServiceJpa.java | 34 +- .../ApplyHolidaysToLoansTasklet.java | 8 +- ...cheduleCalculationPlatformServiceImpl.java | 4 +- ...nReschedulePreviewPlatformServiceImpl.java | 21 +- ...heduleRequestWritePlatformServiceImpl.java | 15 +- .../LoanApplicationValidator.java | 36 +- .../LoanAccrualsProcessingServiceImpl.java | 39 +- .../LoanChargeWritePlatformServiceImpl.java | 11 +- .../service/LoanDisbursementService.java | 66 +- .../service/LoanJournalEntryPoster.java | 29 +- .../service/LoanReadPlatformServiceImpl.java | 8 +- ...WritePlatformServiceJpaRepositoryImpl.java | 45 +- .../starter/LoanAccountConfiguration.java | 41 +- ...LoanAccrualsProcessingServiceImplTest.java | 5 +- ...oanChargeWritePlatformServiceImplTest.java | 31 +- 28 files changed, 995 insertions(+), 776 deletions(-) create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeDataDTO.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeLoanTransactionDTO.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByDTO.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanMapper.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeDataDTO.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeDataDTO.java new file mode 100644 index 000000000..86bcf7ef7 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeDataDTO.java @@ -0,0 +1,49 @@ +/** + * 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 java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class AccountingBridgeDataDTO { + + private Long loanId; + private Long loanProductId; + private Long officeId; + private String currencyCode; + private BigDecimal calculatedInterest; + private boolean cashBasedAccountingEnabled; + private boolean upfrontAccrualBasedAccountingEnabled; + private boolean periodicAccrualBasedAccountingEnabled; + private boolean isAccountTransfer; + private boolean isChargeOff; + private boolean isFraud; + private Long chargeOffReasonCodeValue; + private List newLoanTransactions = new ArrayList<>(); + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeLoanTransactionDTO.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeLoanTransactionDTO.java new file mode 100644 index 000000000..bc3d62561 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeLoanTransactionDTO.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AccountingBridgeLoanTransactionDTO { + + private Long id; + private Long officeId; + private LoanTransactionEnumData type; + private boolean reversed; + private LocalDate date; + private String currencyCode; + private BigDecimal amount; + private BigDecimal netDisbursalAmount; + private BigDecimal principalPortion; + private BigDecimal interestPortion; + private BigDecimal feeChargesPortion; + private BigDecimal penaltyChargesPortion; + private BigDecimal overPaymentPortion; + private String chargeRefundChargeType; + private Long paymentTypeId; + private List loanChargesPaid = new ArrayList<>(); + private BigDecimal principalPaid; + private BigDecimal feePaid; + private BigDecimal penaltyPaid; + private LoanChargeData loanChargeData; + private boolean loanToLoanTransfer; + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByDTO.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByDTO.java new file mode 100644 index 000000000..bd281ca6a --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByDTO.java @@ -0,0 +1,37 @@ +/** + * 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 java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoanChargePaidByDTO { + + private Long chargeId; + private Boolean isPenalty; + private Long loanChargeId; + private BigDecimal amount; + private Integer installmentNumber; + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 460036c47..6110ced7a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -21,9 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.domain; import static org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.CUMULATIVE; import static org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.PROGRESSIVE; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -43,20 +40,16 @@ import jakarta.persistence.UniqueConstraint; import jakarta.persistence.Version; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; -import java.math.MathContext; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; -import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; @@ -67,61 +60,36 @@ import lombok.Setter; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer; -import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator; import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.service.HolidayUtil; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.staff.domain.Staff; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.portfolio.accountdetails.domain.AccountType; -import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; -import org.apache.fineract.portfolio.calendar.domain.Calendar; -import org.apache.fineract.portfolio.calendar.domain.CalendarHistory; -import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; -import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.collateral.domain.LoanCollateral; -import org.apache.fineract.portfolio.common.domain.DayOfWeekType; -import org.apache.fineract.portfolio.common.domain.NthDayType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; -import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO; -import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData; import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; -import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; -import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; -import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; -import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; -import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType; -import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; -import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; -import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; -import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; -import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; -import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; @@ -186,7 +154,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @JoinColumn(name = "product_id", nullable = false) private LoanProduct loanProduct; - @ManyToOne(optional = true, fetch = FetchType.EAGER) + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "fund_id") private Fund fund; @@ -246,7 +214,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { private LocalDate rejectedOnDate; @Setter() - @ManyToOne(optional = true, fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "rejectedon_userid") private AppUser rejectedBy; @@ -255,7 +223,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { private LocalDate withdrawnOnDate; @Setter() - @ManyToOne(optional = true, fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "withdrawnon_userid") private AppUser withdrawnBy; @@ -264,7 +232,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { private LocalDate approvedOnDate; @Setter() - @ManyToOne(optional = true, fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "approvedon_userid") private AppUser approvedBy; @@ -277,7 +245,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { private LocalDate actualDisbursementDate; @Setter() - @ManyToOne(optional = true, fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "disbursedon_userid") private AppUser disbursedBy; @@ -286,7 +254,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { private LocalDate closedOnDate; @Setter() - @ManyToOne(optional = true, fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "closedon_userid") private AppUser closedBy; @@ -298,7 +266,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "rescheduledon_date") private LocalDate rescheduledOnDate; - @ManyToOne(optional = true, fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "rescheduledon_userid") private AppUser rescheduledByUser; @@ -396,7 +364,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "total_recovered_derived", scale = 6, precision = 19) private BigDecimal totalRecovered; - @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true, fetch = FetchType.LAZY) + @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY) private LoanInterestRecalculationDetails loanInterestRecalculationDetails; @Column(name = "is_npa", nullable = false) @@ -439,7 +407,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "is_fraud", nullable = false) private boolean fraud = false; - @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true, fetch = FetchType.LAZY) + @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY) private LoanTopupDetails loanTopupDetails; @OneToMany(fetch = FetchType.LAZY) @@ -709,7 +677,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { * newly created transaction is also added to the Loan on which this method is called. * * If "suppliedTransactionDate" is not passed Id, the transaction date is set to the loans due date if the due date - * is lesser than todays date. If not, the transaction date is set to todays date + * is lesser than today's date. If not, the transaction date is set to today's date */ public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) { if (isProgressiveSchedule()) { @@ -782,9 +750,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { if (transaction.isGreaterThan(chargeAmount)) { final Money principalPortion = Money.zero(currency); final Money interestPortion = Money.zero(currency); - final Money penaltychargesPortion = Money.zero(currency); + final Money penaltyChargesPortion = Money.zero(currency); - transaction.updateComponentsAndTotal(principalPortion, interestPortion, chargeAmount, penaltychargesPortion); + transaction.updateComponentsAndTotal(principalPortion, interestPortion, chargeAmount, penaltyChargesPortion); } else { transactionToRemove = transaction; @@ -874,12 +842,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return this.summary.getTotalWrittenOff(); } - /** - * @param calculationType - * @param percentage - * @param installment - * @return - */ private Money calculateInstallmentChargeAmount(final ChargeCalculationType calculationType, final BigDecimal percentage, final LoanRepaymentScheduleInstallment installment) { Money percentOf = switch (calculationType) { @@ -1113,64 +1075,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { }; } - // This method returns date format and locale if present in the JsonCommand - public Map getDateFormatAndLocale(final JsonCommand jsonCommand) { - Map returnObject = new HashMap<>(); - JsonElement jsonElement = jsonCommand.parsedJson(); - if (jsonElement.isJsonObject()) { - JsonObject topLevel = jsonElement.getAsJsonObject(); - if (topLevel.has(LoanApiConstants.dateFormatParameterName) - && topLevel.get(LoanApiConstants.dateFormatParameterName).isJsonPrimitive()) { - final JsonPrimitive primitive = topLevel.get(LoanApiConstants.dateFormatParameterName).getAsJsonPrimitive(); - returnObject.put(LoanApiConstants.dateFormatParameterName, primitive.getAsString()); - } - if (topLevel.has(LoanApiConstants.localeParameterName) - && topLevel.get(LoanApiConstants.localeParameterName).isJsonPrimitive()) { - final JsonPrimitive primitive = topLevel.get(LoanApiConstants.localeParameterName).getAsJsonPrimitive(); - String localeString = primitive.getAsString(); - returnObject.put(LoanApiConstants.localeParameterName, localeString); - } - } - return returnObject; - } - - public Map parseDisbursementDetails(final JsonObject jsonObject, String dateFormat, Locale locale) { - Map returnObject = new HashMap<>(); - if (jsonObject.get(LoanApiConstants.expectedDisbursementDateParameterName) != null - && jsonObject.get(LoanApiConstants.expectedDisbursementDateParameterName).isJsonPrimitive()) { - final JsonPrimitive primitive = jsonObject.get(LoanApiConstants.expectedDisbursementDateParameterName).getAsJsonPrimitive(); - final String valueAsString = primitive.getAsString(); - if (StringUtils.isNotBlank(valueAsString)) { - LocalDate date = JsonParserHelper.convertFrom(valueAsString, LoanApiConstants.expectedDisbursementDateParameterName, - dateFormat, locale); - if (date != null) { - returnObject.put(LoanApiConstants.expectedDisbursementDateParameterName, date); - } - } - } - - if (jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive() - && StringUtils.isNotBlank(jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString())) { - BigDecimal principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); - returnObject.put(LoanApiConstants.disbursementPrincipalParameterName, principal); - } - - if (jsonObject.has(LoanApiConstants.disbursementIdParameterName) - && jsonObject.get(LoanApiConstants.disbursementIdParameterName).isJsonPrimitive() - && StringUtils.isNotBlank(jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString())) { - Long id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong(); - returnObject.put(LoanApiConstants.disbursementIdParameterName, id); - } - - if (jsonObject.has(LoanApiConstants.loanChargeIdParameterName) - && jsonObject.get(LoanApiConstants.loanChargeIdParameterName).isJsonPrimitive() - && StringUtils.isNotBlank(jsonObject.get(LoanApiConstants.loanChargeIdParameterName).getAsString())) { - returnObject.put(LoanApiConstants.loanChargeIdParameterName, - jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString()); - } - return returnObject; - } - public List fetchLoanTrancheChargeIds() { return getCharges().stream()// .filter(charge -> charge.isTrancheDisbursementCharge() && charge.isActive()) // @@ -1344,56 +1248,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } } - public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO scheduleGeneratorDTO) { - final MathContext mc = MoneyHelper.getMathContext(); - - final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO); - LoanScheduleGenerator loanScheduleGenerator; - if (loanApplicationTerms.isEqualAmortization()) { - if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) { - final LoanScheduleGenerator decliningLoanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory() - .create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE); - Set loanCharges = getActiveCharges(); - LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, - scheduleGeneratorDTO.getHolidayDetailDTO()); - - loanApplicationTerms - .updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged())); - - } - loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(), - InterestMethod.FLAT); - } else { - loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(), - loanApplicationTerms.getInterestMethod()); - } - - return loanScheduleGenerator.generate(mc, loanApplicationTerms, getActiveCharges(), scheduleGeneratorDTO.getHolidayDetailDTO()); - } - - private BigDecimal constructFloatingInterestRates(final BigDecimal annualNominalInterestRate, final FloatingRateDTO floatingRateDTO, - final List loanTermVariations) { - final LocalDate dateValue = null; - final boolean isSpecificToInstallment = false; - BigDecimal interestRate = annualNominalInterestRate; - if (loanProduct.isLinkedToFloatingInterestRate()) { - floatingRateDTO.resetInterestRateDiff(); - Collection applicableRates = loanProduct.fetchInterestRates(floatingRateDTO); - LocalDate interestRateStartDate = DateUtils.getBusinessLocalDate(); - for (FloatingRatePeriodData periodData : applicableRates) { - LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( - LoanEnumerations.loanVariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), - periodData.getInterestRate(), dateValue, isSpecificToInstallment); - if (!DateUtils.isBefore(interestRateStartDate, periodData.getFromDateAsLocalDate())) { - interestRateStartDate = periodData.getFromDateAsLocalDate(); - interestRate = periodData.getInterestRate(); - } - loanTermVariations.add(loanTermVariation); - } - } - return interestRate; - } - public boolean isAutoRepaymentForDownPaymentEnabled() { return this.loanRepaymentScheduleDetail.isEnableDownPayment() && this.loanRepaymentScheduleDetail.isEnableAutoRepaymentForDownPayment(); @@ -1664,7 +1518,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } } - // if total paid in transactions doesnt match repayment schedule then there's an overpayment. + // if total paid in transactions doesn't match repayment schedule then there's an overpayment. return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments); } @@ -1945,7 +1799,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return this.client != null ? this.client.getOffice() : this.group.getOffice(); } - private Boolean isCashBasedAccountingEnabledOnLoanProduct() { + public Boolean isCashBasedAccountingEnabledOnLoanProduct() { return this.loanProduct.isCashBasedAccountingEnabled(); } @@ -1970,165 +1824,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return this.loanProduct.getId(); } - public List> deriveAccountingBridgeDataForChargeOff(final String currencyCode, - final List existingTransactionIds, final List existingReversedTransactionIds, boolean isAccountTransfer) { - - final List> accountingBridgeData = new ArrayList<>(); - final List> newLoanTransactionsBeforeChargeOff = new ArrayList<>(); - final List> newLoanTransactionsAfterChargeOff = new ArrayList<>(); - // get map before charge-off - final Map accountingBridgeDataBeforeChargeOff = buildAccountingMapForChargeOffDateCriteria(currencyCode, - isAccountTransfer, true); - // get map after charge-off - final Map accountingBridgeDataAfterChargeOff = buildAccountingMapForChargeOffDateCriteria(currencyCode, - isAccountTransfer, false); - - // split the transactions according charge-off date - classifyTransactionsBasedOnChargeOffDate(newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, - existingTransactionIds, existingReversedTransactionIds, currencyCode); - - accountingBridgeDataBeforeChargeOff.put("newLoanTransactions", newLoanTransactionsBeforeChargeOff); - accountingBridgeData.add(accountingBridgeDataBeforeChargeOff); - - accountingBridgeDataAfterChargeOff.put("newLoanTransactions", newLoanTransactionsAfterChargeOff); - accountingBridgeData.add(accountingBridgeDataAfterChargeOff); - - return accountingBridgeData; - } - - private void classifyTransactionsBasedOnChargeOffDate(List> newLoanTransactionsBeforeChargeOff, - List> newLoanTransactionsAfterChargeOff, List existingTransactionIds, - List existingReversedTransactionIds, String currencyCode) { - // Before - filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff, currencyCode, existingTransactionIds, - existingReversedTransactionIds, transaction -> DateUtils.isBefore(transaction.getTransactionDate(), getChargedOffOnDate())); - // On - filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, currencyCode, - existingTransactionIds, existingReversedTransactionIds, - transaction -> DateUtils.isEqual(transaction.getTransactionDate(), getChargedOffOnDate())); - // After - filterTransactionsByChargeOffDate(newLoanTransactionsAfterChargeOff, currencyCode, existingTransactionIds, - existingReversedTransactionIds, transaction -> DateUtils.isAfter(transaction.getTransactionDate(), getChargedOffOnDate())); - } - - private Map getAccountingBridgeDataGenericAttributes(final String currencyCode, boolean isAccountTransfer) { - final Map accountingBridgeDataGenericAttributes = new LinkedHashMap<>(); - accountingBridgeDataGenericAttributes.put("loanId", getId()); - accountingBridgeDataGenericAttributes.put("loanProductId", productId()); - accountingBridgeDataGenericAttributes.put("officeId", getOfficeId()); - accountingBridgeDataGenericAttributes.put("currencyCode", currencyCode); - accountingBridgeDataGenericAttributes.put("calculatedInterest", this.summary.getTotalInterestCharged()); - accountingBridgeDataGenericAttributes.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnLoanProduct()); - accountingBridgeDataGenericAttributes.put("upfrontAccrualBasedAccountingEnabled", isUpfrontAccrualAccountingEnabledOnLoanProduct()); - accountingBridgeDataGenericAttributes.put("periodicAccrualBasedAccountingEnabled", - isPeriodicAccrualAccountingEnabledOnLoanProduct()); - accountingBridgeDataGenericAttributes.put("isAccountTransfer", isAccountTransfer); - return accountingBridgeDataGenericAttributes; - } - - private Map buildAccountingMapForChargeOffDateCriteria(final String currencyCode, boolean isAccountTransfer, - boolean isBeforeChargeOffDate) { - final Map accountingBridgeDataChargeOff = new LinkedHashMap<>( - getAccountingBridgeDataGenericAttributes(currencyCode, isAccountTransfer)); - accountingBridgeDataChargeOff.put("isChargeOff", !isBeforeChargeOffDate && isChargedOff()); - accountingBridgeDataChargeOff.put("isFraud", isFraud()); - accountingBridgeDataChargeOff.put("chargeOffReasonCodeValue", fetchChargeOffReasonId()); - return accountingBridgeDataChargeOff; - } - - private void filterTransactionsByChargeOffDate(List> filteredTransactions, final String currencyCode, - final List existingTransactionIds, final List existingReversedTransactionIds, - Predicate chargeOffDateCriteria) { - filteredTransactions.addAll(this.loanTransactions.stream() // - .filter(chargeOffDateCriteria) // - .filter(transaction -> { - boolean isExistingTransaction = existingTransactionIds.contains(transaction.getId()); - boolean isExistingReversedTransaction = existingReversedTransactionIds.contains(transaction.getId()); - - if (transaction.isReversed() && isExistingTransaction && !isExistingReversedTransaction) { - return true; - } else { - return !isExistingTransaction; - } - }) // - .map(transaction -> transaction.toMapData(currencyCode)).toList()); - } - - private void filterTransactionsByChargeOffDate(List> newLoanTransactionsBeforeChargeOff, - List> newLoanTransactionsAfterChargeOff, String currencyCode, List existingTransactionIds, - List existingReversedTransactionIds, Predicate chargeOffDateCriteria) { - - LoanTransaction chargeOffTransaction = this.loanTransactions.stream() // - .filter(LoanTransaction::isChargeOff) // - .filter(LoanTransaction::isNotReversed) // - .findFirst().get(); - - LoanTransaction originalChargeOffTransaction = getOriginalTransactionIfReverseReplayed(chargeOffTransaction); - - this.loanTransactions.stream().filter(chargeOffDateCriteria).forEach(transaction -> { - boolean isExistingTransaction = existingTransactionIds.contains(transaction.getId()); - boolean isExistingReversedTransaction = existingReversedTransactionIds.contains(transaction.getId()); - List> targetList = null; - if ((transaction.isReversed() && isExistingTransaction && !isExistingReversedTransaction)) { - // reversed transactions - LoanTransaction originalTransaction = getOriginalTransactionIfReverseReplayed(transaction); - targetList = originalTransaction.happenedBefore(originalChargeOffTransaction) ? newLoanTransactionsBeforeChargeOff - : newLoanTransactionsAfterChargeOff; - - } else if (!isExistingTransaction) { - // new and replayed transactions - targetList = transaction.happenedBefore(chargeOffTransaction) ? newLoanTransactionsBeforeChargeOff - : newLoanTransactionsAfterChargeOff; - } - if (targetList != null) { - targetList.add(transaction.toMapData(currencyCode)); - } - }); - } - - private LoanTransaction getOriginalTransactionIfReverseReplayed(LoanTransaction loanTransaction) { - if (!loanTransaction.getLoanTransactionRelations().isEmpty()) { - return loanTransaction.getLoanTransactionRelations().stream() - .filter(tr -> LoanTransactionRelationTypeEnum.REPLAYED.equals(tr.getRelationType())).map(tr -> tr.getToTransaction()) - .collect(Collectors.toList()).stream().sorted(Comparator.comparingLong(LoanTransaction::getId)).findFirst() - .orElse(loanTransaction); - } - return loanTransaction; - } - - public Map deriveAccountingBridgeData(final String currencyCode, final List existingTransactionIds, - final List existingReversedTransactionIds, boolean isAccountTransfer) { - - final Map accountingBridgeData = new LinkedHashMap<>(); - accountingBridgeData.put("loanId", getId()); - accountingBridgeData.put("loanProductId", productId()); - accountingBridgeData.put("officeId", getOfficeId()); - accountingBridgeData.put("currencyCode", currencyCode); - accountingBridgeData.put("calculatedInterest", this.summary.getTotalInterestCharged()); - accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnLoanProduct()); - accountingBridgeData.put("upfrontAccrualBasedAccountingEnabled", isUpfrontAccrualAccountingEnabledOnLoanProduct()); - accountingBridgeData.put("periodicAccrualBasedAccountingEnabled", isPeriodicAccrualAccountingEnabledOnLoanProduct()); - accountingBridgeData.put("isAccountTransfer", isAccountTransfer); - accountingBridgeData.put("isChargeOff", isChargedOff()); - accountingBridgeData.put("chargeOffReasonCodeValue", fetchChargeOffReasonId()); - - accountingBridgeData.put("isFraud", isFraud()); - - final List> newLoanTransactions = new ArrayList<>(); - for (final LoanTransaction transaction : this.loanTransactions) { - if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId()) - && !existingReversedTransactionIds.contains(transaction.getId())) { - newLoanTransactions.add(transaction.toMapData(currencyCode)); - } else if (!existingTransactionIds.contains(transaction.getId())) { - newLoanTransactions.add(transaction.toMapData(currencyCode)); - } - } - - accountingBridgeData.put("newLoanTransactions", newLoanTransactions); - return accountingBridgeData; - } - - private Long fetchChargeOffReasonId() { + public Long fetchChargeOffReasonId() { return isChargedOff() && getChargeOffReason() != null ? getChargeOffReason().getId() : null; } @@ -2223,25 +1919,23 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery(); final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType); - LocalDate newRepaymentDate = null; - LocalDate seedDate = meetingStartDate; + LocalDate newRepaymentDate; LocalDate latestRepaymentDate = null; List installments = getRepaymentScheduleInstallments(); for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); // FIXME: AA this won't update repayment dates before current date. - - if (DateUtils.isAfter(oldDueDate, seedDate) && DateUtils.isDateInTheFuture(oldDueDate)) { - newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, seedDate, oldDueDate, loanRepaymentInterval, - frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays); + if (DateUtils.isAfter(oldDueDate, meetingStartDate) && DateUtils.isDateInTheFuture(oldDueDate)) { + newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate, + loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays); final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType, loanRepaymentInterval, tmpFromDate); if (DateUtils.isAfter(newRepaymentDate, maxDateLimitForNewRepayment)) { - newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate, loanRepaymentInterval, - frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays); + newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate, + loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays); } if (isHolidayEnabled) { @@ -2292,10 +1986,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } public Integer getLoanProductLoanCounter() { - if (this.loanProductCounter == null) { - return 0; - } - return this.loanProductCounter; + return Objects.requireNonNullElse(this.loanProductCounter, 0); } public void updateClientLoanCounter(final Integer newLoanCounter) { @@ -2510,76 +2201,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return (this.actualMaturityDate != null) ? (referenceDate.compareTo(this.actualMaturityDate) >= 0) : false; } - public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO) { - final Integer loanTermFrequency = this.termFrequency; - NthDayType nthDayType = null; - DayOfWeekType dayOfWeekType = null; - final List disbursementData = new ArrayList<>(); - for (LoanDisbursementDetails disbursementDetails : getDisbursementDetails()) { - disbursementData.add(disbursementDetails.toData()); - } - - Calendar calendar = scheduleGeneratorDTO.getCalendar(); - if (calendar != null) { - nthDayType = CalendarUtils.getRepeatsOnNthDayOfMonth(calendar.getRecurrence()); - dayOfWeekType = DayOfWeekType.fromInt(CalendarUtils.getRepeatsOnDay(calendar.getRecurrence()).getValue()); - } - HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); - CalendarInstance restCalendarInstance = null; - CalendarInstance compoundingCalendarInstance = null; - RecalculationFrequencyType recalculationFrequencyType = null; - InterestRecalculationCompoundingMethod compoundingMethod = null; - RecalculationFrequencyType compoundingFrequencyType = null; - LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - CalendarHistoryDataWrapper calendarHistoryDataWrapper; - RepaymentStartDateType repaymentStartDateType = this.getLoanProduct().getRepaymentStartDateType(); - boolean allowCompoundingOnEod = false; - if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation(); - compoundingCalendarInstance = scheduleGeneratorDTO.getCompoundingCalendarInstance(); - recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType(); - compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); - compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); - rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); - allowCompoundingOnEod = this.loanInterestRecalculationDetails.allowCompoundingOnEod(); - } - calendar = scheduleGeneratorDTO.getCalendar(); - calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper(); - - BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); - FloatingRateDTO floatingRateDTO = scheduleGeneratorDTO.getFloatingRateDTO(); - List loanTermVariations = new ArrayList<>(); - annualNominalInterestRate = constructLoanTermVariations(floatingRateDTO, annualNominalInterestRate, loanTermVariations); - LocalDate interestChargedFromDate = getInterestChargedFromDate(); - if (interestChargedFromDate == null && scheduleGeneratorDTO.isInterestChargedFromDateAsDisbursementDateEnabled()) { - interestChargedFromDate = getDisbursementDate(); - } - - return LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getCurrency(), loanTermFrequency, this.termPeriodFrequencyType, - nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), - scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), this.loanRepaymentScheduleDetail, - this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, - interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), - this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, - compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), - rescheduleStrategyMethod, calendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, - calendarHistoryDataWrapper, scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), - holidayDetailDTO, allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(), - scheduleGeneratorDTO.isInterestToBeRecoveredFirstWhenGreaterThanEMI(), this.fixedPrincipalPercentagePerInstallment, - scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, getSubmittedOnDate()); - } - - public BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate, - List loanTermVariations) { - for (LoanTermVariations variationTerms : this.loanTermVariations) { - if (variationTerms.isActive()) { - loanTermVariations.add(variationTerms.toData()); - } - } - annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations); - return annualNominalInterestRate; - } - public LocalDate fetchInterestRecalculateFromDate() { LocalDate recalculatedOn; if (this.interestRecalculatedOn == null) { @@ -2602,7 +2223,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { Money transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency()); if (loanTransaction.isOverPaid()) { // in case of advanced payment strategy and creditAllocations the full amount is recognized first - if (this.getCreditAllocationRules() != null && this.getCreditAllocationRules().size() > 0) { + if (this.getCreditAllocationRules() != null && !this.getCreditAllocationRules().isEmpty()) { Money payedPrincipal = loanTransaction.getLoanTransactionToRepaymentScheduleMappings().stream() // .map(mapping -> mapping.getPrincipalPortion(getCurrency())) // .reduce(Money.zero(getCurrency()), Money::plus); @@ -2690,137 +2311,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return getRepaymentScheduleInstallments().stream().filter(predicate).toList(); } - /** - * @return loan disbursement data - **/ - public List getDisbursementData() { - Iterator iterator = this.getDisbursementDetails().iterator(); - List disbursementData = new ArrayList<>(); - - while (iterator.hasNext()) { - LoanDisbursementDetails loanDisbursementDetails = iterator.next(); - - LocalDate expectedDisbursementDate = null; - LocalDate actualDisbursementDate = null; - - if (loanDisbursementDetails.expectedDisbursementDate() != null) { - expectedDisbursementDate = loanDisbursementDetails.expectedDisbursementDate(); - } - - if (loanDisbursementDetails.actualDisbursementDate() != null) { - actualDisbursementDate = loanDisbursementDetails.actualDisbursementDate(); - } - BigDecimal waivedChargeAmount = null; - disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate, - loanDisbursementDetails.principal(), this.netDisbursalAmount, null, null, waivedChargeAmount)); - } - - return disbursementData; - } - - /** - * @return application terms of the Loan object - **/ - @SuppressWarnings({ "unused" }) - public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency, - final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, - final FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays, - final HolidayDetailDTO holidayDetailDTO) { - LoanProduct loanProduct = loanProduct(); - final MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getCurrency(); - - final Integer loanTermFrequency = getTermFrequency(); - final PeriodFrequencyType loanTermPeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(); - NthDayType nthDayType = null; - DayOfWeekType dayOfWeekType = null; - if (loanCalendar != null) { - nthDayType = CalendarUtils.getRepeatsOnNthDayOfMonth(loanCalendar.getRecurrence()); - CalendarWeekDaysType getRepeatsOnDay = CalendarUtils.getRepeatsOnDay(loanCalendar.getRecurrence()); - Integer getRepeatsOnDayValue = null; - if (getRepeatsOnDay != null) { - getRepeatsOnDayValue = getRepeatsOnDay.getValue(); - } - if (getRepeatsOnDayValue != null) { - dayOfWeekType = DayOfWeekType.fromInt(getRepeatsOnDayValue); - } - } - - final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments(); - final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery(); - final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType(); - - final AmortizationMethod amortizationMethod = this.loanRepaymentScheduleDetail.getAmortizationMethod(); - - final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = this.loanRepaymentScheduleDetail - .getInterestCalculationPeriodMethod(); - - final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod(); - final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(); - - BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); - final Money principalMoney = this.loanRepaymentScheduleDetail.getPrincipal(); - - final LocalDate expectedDisbursementDate = getExpectedDisbursedOnLocalDate(); - final LocalDate repaymentsStartingFromDate = getExpectedFirstRepaymentOnDate(); - LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate; - - // TODO get calender linked to loan if any exist. As of 17-07-2014, - // could not find a link in the database. - // skip for now and set the Calender object to null - // Calendar loanCalendar = null; - // The calendar instance might be null if the loan is not connected - // To a calendar object - // if (loanCalendarInstance != null) { - // loanCalendar = loanCalendarInstance.getCalendar(); - // } - - final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.getGraceOnPrincipalPayment(); - final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.getGraceOnInterestPayment(); - final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.getGraceOnInterestCharged(); - final LocalDate interestChargedFromDate = getInterestChargedFromDate(); - final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnArrearsAgeing(); - - final Money inArrearsToleranceMoney = this.loanRepaymentScheduleDetail.getInArrearsTolerance(); - final BigDecimal emiAmount = getFixedEmiAmount(); - final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance(); - - final List disbursementData = getDisbursementData(); - - CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - if (loanCalendar != null) { - Set calendarHistory = loanCalendar.getCalendarHistory(); - calendarHistoryDataWrapper = new CalendarHistoryDataWrapper(calendarHistory); - } - - RecalculationFrequencyType recalculationFrequencyType = null; - InterestRecalculationCompoundingMethod compoundingMethod = null; - RecalculationFrequencyType compoundingFrequencyType = null; - LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - RepaymentStartDateType repaymentStartDateType = loanProduct.getRepaymentStartDateType(); - boolean allowCompoundingOnEod = false; - if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType(); - compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); - compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); - rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); - allowCompoundingOnEod = this.loanInterestRecalculationDetails.allowCompoundingOnEod(); - } - - List loanTermVariations = new ArrayList<>(); - annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations); - - return LoanApplicationTerms.assembleFrom(applicationCurrency.toData(), loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, - dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, - inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, - maxOutstandingBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), - this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, - compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), - rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, - calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst, holidayDetailDTO, allowCompoundingOnEod, false, - false, this.fixedPrincipalPercentagePerInstallment, false, repaymentStartDateType, getSubmittedOnDate()); - } - public void updateRescheduledByUser(AppUser rescheduledByUser) { this.rescheduledByUser = rescheduledByUser; } @@ -2993,7 +2483,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } public LoanRepaymentScheduleInstallment fetchLoanForeclosureDetail(final LocalDate closureDate) { - Money[] receivables = retriveIncomeOutstandingTillDate(closureDate); + Money[] receivables = retrieveIncomeOutstandingTillDate(closureDate); Money totalPrincipal = Money.of(getCurrency(), this.getSummary().getTotalPrincipalOutstanding()); totalPrincipal = totalPrincipal.minus(receivables[3]); final Set compoundingDetails = null; @@ -3002,7 +2492,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { receivables[0].getAmount(), receivables[1].getAmount(), receivables[2].getAmount(), false, compoundingDetails); } - public Money[] retriveIncomeOutstandingTillDate(final LocalDate paymentDate) { + private Money[] retrieveIncomeOutstandingTillDate(final LocalDate paymentDate) { Money[] balances = new Money[4]; final MonetaryCurrency currency = getCurrency(); Money interest = Money.zero(currency); @@ -3058,13 +2548,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { Money penaltyAccoutedForCurrentPeriod = Money.zero(getCurrency()); Money feeForCurrentPeriod = Money.zero(getCurrency()); Money feeAccountedForCurrentPeriod = Money.zero(getCurrency()); - Money interestForCurrentPeriod = Money.zero(getCurrency()); - Money interestAccountedForCurrentPeriod = Money.zero(getCurrency()); int totalPeriodDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), installment.getDueDate()); int tillDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), paymentDate); - interestForCurrentPeriod = Money.of(getCurrency(), BigDecimal + Money interestForCurrentPeriod = Money.of(getCurrency(), BigDecimal .valueOf(calculateInterestForDays(totalPeriodDays, installment.getInterestCharged(getCurrency()).getAmount(), tillDays))); - interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()).plus(installment.getInterestPaid(getCurrency())); + Money interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()) + .plus(installment.getInterestPaid(getCurrency())); for (LoanCharge loanCharge : this.charges) { if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) { boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), paymentDate, isFirstNormalInstallment); @@ -3101,7 +2590,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return balances; } - public Money[] retriveIncomeForOverlappingPeriod(final LocalDate paymentDate) { + public Money[] retrieveIncomeForOverlappingPeriod(final LocalDate paymentDate) { Money[] balances = new Money[3]; final MonetaryCurrency currency = getCurrency(); balances[0] = balances[1] = balances[2] = Money.zero(currency); @@ -3165,19 +2654,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { this.fraud = value; } - public BigDecimal getFirstDisbursalAmount() { - BigDecimal firstDisbursalAmount; - - if (this.isMultiDisburmentLoan()) { - List disbursementData = getDisbursementData(); - Collections.sort(disbursementData); - firstDisbursalAmount = disbursementData.get(disbursementData.size() - 1).getPrincipal(); - } else { - firstDisbursalAmount = this.getLoanRepaymentScheduleDetail().getPrincipal().getAmount(); - } - return firstDisbursalAmount; - } - public void setTopupLoanDetails(LoanTopupDetails topupLoanDetails) { this.loanTopupDetails = topupLoanDetails; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java new file mode 100644 index 000000000..c2d3916b9 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java @@ -0,0 +1,251 @@ +/** + * 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.mapper; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO; +import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByDTO; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; +import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class LoanAccountingBridgeMapper { + + public List deriveAccountingBridgeDataForChargeOff(final String currencyCode, + final List existingTransactionIds, final List existingReversedTransactionIds, final boolean isAccountTransfer, + final Loan loan) { + final List newLoanTransactionsBeforeChargeOff = new ArrayList<>(); + final List newLoanTransactionsAfterChargeOff = new ArrayList<>(); + + // split the transactions according charge-off date + classifyTransactionsBasedOnChargeOffDate(newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, + existingTransactionIds, existingReversedTransactionIds, currencyCode, loan); + + AccountingBridgeDataDTO beforeChargeOff = new AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(), + currencyCode, loan.getSummary().getTotalInterestCharged(), loan.isCashBasedAccountingEnabledOnLoanProduct(), + loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(), + isAccountTransfer, false, loan.isFraud(), loan.fetchChargeOffReasonId(), newLoanTransactionsBeforeChargeOff); + + AccountingBridgeDataDTO afterChargeOff = new AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(), + currencyCode, loan.getSummary().getTotalInterestCharged(), loan.isCashBasedAccountingEnabledOnLoanProduct(), + loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(), + isAccountTransfer, true, loan.isFraud(), loan.fetchChargeOffReasonId(), newLoanTransactionsAfterChargeOff); + + List result = new ArrayList<>(); + result.add(beforeChargeOff); + result.add(afterChargeOff); + return result; + } + + public AccountingBridgeDataDTO deriveAccountingBridgeData(final String currencyCode, final List existingTransactionIds, + final List existingReversedTransactionIds, final boolean isAccountTransfer, final Loan loan) { + final List newLoanTransactions = new ArrayList<>(); + for (final LoanTransaction transaction : loan.getLoanTransactions()) { + if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId()) + && !existingReversedTransactionIds.contains(transaction.getId())) { + newLoanTransactions.add(mapToLoanTransactionData(transaction, currencyCode)); + } else if (!existingTransactionIds.contains(transaction.getId())) { + newLoanTransactions.add(mapToLoanTransactionData(transaction, currencyCode)); + } + } + + return new AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(), currencyCode, + loan.getSummary().getTotalInterestCharged(), loan.isCashBasedAccountingEnabledOnLoanProduct(), + loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(), + isAccountTransfer, loan.isChargedOff(), loan.isFraud(), loan.fetchChargeOffReasonId(), newLoanTransactions); + } + + public AccountingBridgeLoanTransactionDTO mapToLoanTransactionData(final LoanTransaction transaction, final String currencyCode) { + final AccountingBridgeLoanTransactionDTO transactionDTO = new AccountingBridgeLoanTransactionDTO(); + + transactionDTO.setId(transaction.getId()); + transactionDTO.setOfficeId(transaction.getOffice().getId()); + transactionDTO.setType(LoanEnumerations.transactionType(transaction.getTypeOf())); + transactionDTO.setReversed(transaction.isReversed()); + transactionDTO.setDate(transaction.getTransactionDate()); + transactionDTO.setCurrencyCode(currencyCode); + transactionDTO.setAmount(transaction.getAmount()); + transactionDTO.setNetDisbursalAmount(transaction.getLoan().getNetDisbursalAmount()); + + if (transactionDTO.getType().isChargeback() && (transaction.getLoan().getCreditAllocationRules() == null + || transaction.getLoan().getCreditAllocationRules().isEmpty())) { + transactionDTO.setPrincipalPortion(transaction.getAmount()); + } else { + transactionDTO.setPrincipalPortion(transaction.getPrincipalPortion()); + } + + transactionDTO.setInterestPortion(transaction.getInterestPortion()); + transactionDTO.setFeeChargesPortion(transaction.getFeeChargesPortion()); + transactionDTO.setPenaltyChargesPortion(transaction.getPenaltyChargesPortion()); + transactionDTO.setOverPaymentPortion(transaction.getOverPaymentPortion()); + + if (transactionDTO.getType().isChargeRefund()) { + transactionDTO.setChargeRefundChargeType(transaction.getChargeRefundChargeType()); + } + + if (transaction.getPaymentDetail() != null) { + transactionDTO.setPaymentTypeId(transaction.getPaymentDetail().getPaymentType().getId()); + } + + if (!transaction.getLoanChargesPaid().isEmpty()) { + List loanChargesPaidData = new ArrayList<>(); + for (final LoanChargePaidBy chargePaidBy : transaction.getLoanChargesPaid()) { + final LoanChargePaidByDTO loanChargePaidData = new LoanChargePaidByDTO(); + loanChargePaidData.setChargeId(chargePaidBy.getLoanCharge().getCharge().getId()); + loanChargePaidData.setIsPenalty(chargePaidBy.getLoanCharge().isPenaltyCharge()); + loanChargePaidData.setLoanChargeId(chargePaidBy.getLoanCharge().getId()); + loanChargePaidData.setAmount(chargePaidBy.getAmount()); + loanChargePaidData.setInstallmentNumber(chargePaidBy.getInstallmentNumber()); + + loanChargesPaidData.add(loanChargePaidData); + } + transactionDTO.setLoanChargesPaid(loanChargesPaidData); + } + + if (transactionDTO.getType().isChargeback() && transaction.getOverPaymentPortion() != null + && transaction.getOverPaymentPortion().compareTo(BigDecimal.ZERO) > 0) { + BigDecimal principalPaid = transaction.getOverPaymentPortion(); + BigDecimal feePaid = BigDecimal.ZERO; + BigDecimal penaltyPaid = BigDecimal.ZERO; + if (!transaction.getLoanTransactionToRepaymentScheduleMappings().isEmpty()) { + principalPaid = transaction.getLoanTransactionToRepaymentScheduleMappings().stream() + .map(mapping -> Optional.ofNullable(mapping.getPrincipalPortion()).orElse(BigDecimal.ZERO)) + .reduce(BigDecimal.ZERO, BigDecimal::add); + feePaid = transaction.getLoanTransactionToRepaymentScheduleMappings().stream() + .map(mapping -> Optional.ofNullable(mapping.getFeeChargesPortion()).orElse(BigDecimal.ZERO)) + .reduce(BigDecimal.ZERO, BigDecimal::add); + penaltyPaid = transaction.getLoanTransactionToRepaymentScheduleMappings().stream() + .map(mapping -> Optional.ofNullable(mapping.getPenaltyChargesPortion()).orElse(BigDecimal.ZERO)) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + transactionDTO.setPrincipalPaid(principalPaid); + transactionDTO.setFeePaid(feePaid); + transactionDTO.setPenaltyPaid(penaltyPaid); + } + + LoanTransactionRelation loanTransactionRelation = transaction.getLoanTransactionRelations().stream() + .filter(e -> LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT.equals(e.getRelationType())).findAny().orElse(null); + if (loanTransactionRelation != null) { + LoanCharge loanCharge = loanTransactionRelation.getToCharge(); + transactionDTO.setLoanChargeData(loanCharge.toData()); + } + + transactionDTO.setLoanToLoanTransfer(false); + + return transactionDTO; + } + + private void classifyTransactionsBasedOnChargeOffDate(final List newLoanTransactionsBeforeChargeOff, + final List newLoanTransactionsAfterChargeOff, final List existingTransactionIds, + final List existingReversedTransactionIds, final String currencyCode, final Loan loan) { + // Before + filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff, currencyCode, existingTransactionIds, + existingReversedTransactionIds, + transaction -> DateUtils.isBefore(transaction.getTransactionDate(), loan.getChargedOffOnDate()), loan); + // On + filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, currencyCode, + existingTransactionIds, existingReversedTransactionIds, + transaction -> DateUtils.isEqual(transaction.getTransactionDate(), loan.getChargedOffOnDate()), loan); + // After + filterTransactionsByChargeOffDate(newLoanTransactionsAfterChargeOff, currencyCode, existingTransactionIds, + existingReversedTransactionIds, + transaction -> DateUtils.isAfter(transaction.getTransactionDate(), loan.getChargedOffOnDate()), loan); + } + + private void filterTransactionsByChargeOffDate(final List filteredTransactions, + final String currencyCode, final List existingTransactionIds, final List existingReversedTransactionIds, + final Predicate chargeOffDateCriteria, final Loan loan) { + filteredTransactions.addAll(loan.getLoanTransactions().stream() // + .filter(chargeOffDateCriteria) // + .filter(transaction -> { + boolean isExistingTransaction = existingTransactionIds.contains(transaction.getId()); + boolean isExistingReversedTransaction = existingReversedTransactionIds.contains(transaction.getId()); + + if (transaction.isReversed() && isExistingTransaction && !isExistingReversedTransaction) { + return true; + } else { + return !isExistingTransaction; + } + }) // + .map(transaction -> mapToLoanTransactionData(transaction, currencyCode)).toList()); + } + + private void filterTransactionsByChargeOffDate(final List newLoanTransactionsBeforeChargeOff, + final List newLoanTransactionsAfterChargeOff, final String currencyCode, + final List existingTransactionIds, final List existingReversedTransactionIds, + final Predicate chargeOffDateCriteria, final Loan loan) { + final Optional chargeOffTransactionOptional = loan.getLoanTransactions().stream() // + .filter(LoanTransaction::isChargeOff) // + .filter(LoanTransaction::isNotReversed) // + .findFirst(); + + if (chargeOffTransactionOptional.isEmpty()) { + return; + } + + final LoanTransaction chargeOffTransaction = chargeOffTransactionOptional.get(); + final LoanTransaction originalChargeOffTransaction = getOriginalTransactionIfReverseReplayed(chargeOffTransaction); + + loan.getLoanTransactions().stream().filter(chargeOffDateCriteria).forEach(transaction -> { + boolean isExistingTransaction = existingTransactionIds.contains(transaction.getId()); + boolean isExistingReversedTransaction = existingReversedTransactionIds.contains(transaction.getId()); + List targetList = null; + if ((transaction.isReversed() && isExistingTransaction && !isExistingReversedTransaction)) { + // reversed transactions + LoanTransaction originalTransaction = getOriginalTransactionIfReverseReplayed(transaction); + targetList = originalTransaction.happenedBefore(originalChargeOffTransaction) ? newLoanTransactionsBeforeChargeOff + : newLoanTransactionsAfterChargeOff; + + } else if (!isExistingTransaction) { + // new and replayed transactions + targetList = transaction.happenedBefore(chargeOffTransaction) ? newLoanTransactionsBeforeChargeOff + : newLoanTransactionsAfterChargeOff; + } + if (targetList != null) { + targetList.add(mapToLoanTransactionData(transaction, currencyCode)); + } + }); + } + + private LoanTransaction getOriginalTransactionIfReverseReplayed(final LoanTransaction loanTransaction) { + if (!loanTransaction.getLoanTransactionRelations().isEmpty()) { + return loanTransaction.getLoanTransactionRelations().stream() + .filter(tr -> LoanTransactionRelationTypeEnum.REPLAYED.equals(tr.getRelationType())) + .map(LoanTransactionRelation::getToTransaction).toList().stream().min(Comparator.comparingLong(LoanTransaction::getId)) + .orElse(loanTransaction); + } + return loanTransaction; + } + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanMapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanMapper.java new file mode 100644 index 000000000..5a226c849 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanMapper.java @@ -0,0 +1,102 @@ +/** + * 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.mapper; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; +import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; +import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class LoanMapper { + + private final LoanTermVariationsMapper loanTermVariationsMapper; + + public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO scheduleGeneratorDTO, final Loan loan) { + final MathContext mc = MoneyHelper.getMathContext(); + + final LoanApplicationTerms loanApplicationTerms = loanTermVariationsMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, + loan); + LoanScheduleGenerator loanScheduleGenerator; + if (loanApplicationTerms.isEqualAmortization()) { + if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) { + final LoanScheduleGenerator decliningLoanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory() + .create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE); + Set loanCharges = loan.getActiveCharges(); + LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, + scheduleGeneratorDTO.getHolidayDetailDTO()); + + loanApplicationTerms + .updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged())); + + } + loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(), + InterestMethod.FLAT); + } else { + loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(), + loanApplicationTerms.getInterestMethod()); + } + + return loanScheduleGenerator.generate(mc, loanApplicationTerms, loan.getActiveCharges(), + scheduleGeneratorDTO.getHolidayDetailDTO()); + } + + public List getDisbursementData(final Loan loan) { + Iterator iterator = loan.getDisbursementDetails().iterator(); + List disbursementData = new ArrayList<>(); + + while (iterator.hasNext()) { + LoanDisbursementDetails loanDisbursementDetails = iterator.next(); + + LocalDate expectedDisbursementDate = null; + LocalDate actualDisbursementDate = null; + + if (loanDisbursementDetails.expectedDisbursementDate() != null) { + expectedDisbursementDate = loanDisbursementDetails.expectedDisbursementDate(); + } + + if (loanDisbursementDetails.actualDisbursementDate() != null) { + actualDisbursementDate = loanDisbursementDetails.actualDisbursementDate(); + } + BigDecimal waivedChargeAmount = null; + disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate, + loanDisbursementDetails.principal(), loan.getNetDisbursalAmount(), null, null, waivedChargeAmount)); + } + + return disbursementData; + } + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java new file mode 100644 index 000000000..a11480a64 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java @@ -0,0 +1,148 @@ +/** + * 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.mapper; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; +import org.apache.fineract.portfolio.calendar.domain.Calendar; +import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; +import org.apache.fineract.portfolio.common.domain.DayOfWeekType; +import org.apache.fineract.portfolio.common.domain.NthDayType; +import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO; +import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; +import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; +import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; +import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; +import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; +import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; +import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; +import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; +import org.springframework.stereotype.Component; + +@Component +public class LoanTermVariationsMapper { + + public BigDecimal constructLoanTermVariations(final FloatingRateDTO floatingRateDTO, final BigDecimal annualNominalInterestRate, + final List loanTermVariations, final Loan loan) { + for (LoanTermVariations variationTerms : loan.getLoanTermVariations()) { + if (variationTerms.isActive()) { + loanTermVariations.add(variationTerms.toData()); + } + } + + return constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations, loan); + } + + public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO, final Loan loan) { + final Integer loanTermFrequency = loan.getTermFrequency(); + NthDayType nthDayType = null; + DayOfWeekType dayOfWeekType = null; + final List disbursementData = new ArrayList<>(); + for (LoanDisbursementDetails disbursementDetails : loan.getDisbursementDetails()) { + disbursementData.add(disbursementDetails.toData()); + } + + Calendar calendar = scheduleGeneratorDTO.getCalendar(); + if (calendar != null) { + nthDayType = CalendarUtils.getRepeatsOnNthDayOfMonth(calendar.getRecurrence()); + dayOfWeekType = DayOfWeekType.fromInt(CalendarUtils.getRepeatsOnDay(calendar.getRecurrence()).getValue()); + } + HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); + CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; + RecalculationFrequencyType recalculationFrequencyType = null; + InterestRecalculationCompoundingMethod compoundingMethod = null; + RecalculationFrequencyType compoundingFrequencyType = null; + LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + CalendarHistoryDataWrapper calendarHistoryDataWrapper; + RepaymentStartDateType repaymentStartDateType = loan.getLoanProduct().getRepaymentStartDateType(); + boolean allowCompoundingOnEod = false; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation(); + compoundingCalendarInstance = scheduleGeneratorDTO.getCompoundingCalendarInstance(); + recalculationFrequencyType = loan.getLoanInterestRecalculationDetails().getRestFrequencyType(); + compoundingMethod = loan.getLoanInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(); + compoundingFrequencyType = loan.getLoanInterestRecalculationDetails().getCompoundingFrequencyType(); + rescheduleStrategyMethod = loan.getLoanInterestRecalculationDetails().getRescheduleStrategyMethod(); + allowCompoundingOnEod = loan.getLoanInterestRecalculationDetails().allowCompoundingOnEod(); + } + calendar = scheduleGeneratorDTO.getCalendar(); + calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper(); + + BigDecimal annualNominalInterestRate = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate(); + FloatingRateDTO floatingRateDTO = scheduleGeneratorDTO.getFloatingRateDTO(); + List loanTermVariations = new ArrayList<>(); + annualNominalInterestRate = constructLoanTermVariations(floatingRateDTO, annualNominalInterestRate, loanTermVariations, loan); + LocalDate interestChargedFromDate = loan.getInterestChargedFromDate(); + if (interestChargedFromDate == null && scheduleGeneratorDTO.isInterestChargedFromDateAsDisbursementDateEnabled()) { + interestChargedFromDate = loan.getDisbursementDate(); + } + + return LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getCurrency(), loanTermFrequency, loan.getTermPeriodFrequencyType(), + nthDayType, dayOfWeekType, loan.getDisbursementDate(), loan.getExpectedFirstRepaymentOnDate(), + scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), loan.getInArrearsTolerance(), + loan.getLoanRepaymentScheduleDetail(), loan.getLoanProduct().isMultiDisburseLoan(), loan.getFixedEmiAmount(), + disbursementData, loan.getMaxOutstandingLoanBalance(), interestChargedFromDate, + loan.getLoanProduct().getPrincipalThresholdForLastInstallment(), loan.getLoanProduct().getInstallmentAmountInMultiplesOf(), + recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, + loan.getLoanProduct().preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, calendar, + loan.getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper, + scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), holidayDetailDTO, + allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(), + scheduleGeneratorDTO.isInterestToBeRecoveredFirstWhenGreaterThanEMI(), loan.getFixedPrincipalPercentagePerInstallment(), + scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, loan.getSubmittedOnDate()); + } + + private BigDecimal constructFloatingInterestRates(final BigDecimal annualNominalInterestRate, final FloatingRateDTO floatingRateDTO, + final List loanTermVariations, final Loan loan) { + final LocalDate dateValue = null; + final boolean isSpecificToInstallment = false; + BigDecimal interestRate = annualNominalInterestRate; + if (loan.getLoanProduct().isLinkedToFloatingInterestRate()) { + floatingRateDTO.resetInterestRateDiff(); + Collection applicableRates = loan.getLoanProduct().fetchInterestRates(floatingRateDTO); + LocalDate interestRateStartDate = DateUtils.getBusinessLocalDate(); + for (FloatingRatePeriodData periodData : applicableRates) { + LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( + LoanEnumerations.loanVariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), + periodData.getInterestRate(), dateValue, isSpecificToInstallment); + if (!DateUtils.isBefore(interestRateStartDate, periodData.getFromDateAsLocalDate())) { + interestRateStartDate = periodData.getFromDateAsLocalDate(); + interestRate = periodData.getInterestRate(); + } + loanTermVariations.add(loanTermVariation); + } + } + return interestRate; + } + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java index 057b4b6b3..a37485088 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java @@ -29,6 +29,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper; import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent; @RequiredArgsConstructor @@ -36,6 +37,7 @@ public class LoanScheduleService { private final LoanChargeService loanChargeService; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final LoanMapper loanMapper; private final LoanTransactionProcessingService loadTransactionProcessingService; private final LoanScheduleComponent loanSchedule; @@ -43,7 +45,7 @@ public class LoanScheduleService { * Ability to regenerate the repayment schedule based on the loans current details/state. */ public void regenerateRepaymentSchedule(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) { - final LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO); + final LoanScheduleModel loanScheduleModel = loanMapper.regenerateScheduleModel(scheduleGeneratorDTO, loan); if (loanScheduleModel == null) { return; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java index de9b8895a..4f0a35626 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java @@ -41,6 +41,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Tra import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanTermVariationsMapper; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.springframework.stereotype.Service; @@ -49,6 +50,7 @@ import org.springframework.stereotype.Service; public class LoanTransactionProcessingService { private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; + private final LoanTermVariationsMapper loanMapper; public ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction, TransactionCtx ctx) { @@ -103,7 +105,7 @@ public class LoanTransactionProcessingService { final MathContext mc = MoneyHelper.getMathContext(); - final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(generatorDTO); + final LoanApplicationTerms loanApplicationTerms = loanMapper.constructLoanApplicationTerms(generatorDTO, loan); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor( loan.getTransactionProcessingStrategyCode()); @@ -119,7 +121,7 @@ public class LoanTransactionProcessingService { final MathContext mc = MoneyHelper.getMathContext(); final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod(); - final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + final LoanApplicationTerms loanApplicationTerms = loanMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, loan); final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory() .create(loanApplicationTerms.getLoanScheduleType(), interestMethod); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index c000482be..6be56815f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -29,8 +29,8 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.closure.domain.GLClosure; import org.apache.fineract.accounting.closure.domain.GLClosureRepository; +import org.apache.fineract.accounting.common.AccountingConstants; import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan; -import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares; @@ -68,12 +68,11 @@ import org.apache.fineract.portfolio.PortfolioProductType; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; -import org.apache.fineract.portfolio.client.domain.ClientTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; +import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData; -import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionEnumData; import org.springframework.dao.DataAccessException; @@ -92,55 +91,53 @@ public class AccountingProcessorHelper { private final GLClosureRepository closureRepository; private final GLAccountRepository glAccountRepository; private final OfficeRepository officeRepository; - private final LoanTransactionRepository loanTransactionRepository; - private final ClientTransactionRepository clientTransactionRepository; - private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; private final AccountTransfersReadPlatformService accountTransfersReadPlatformService; private final ChargeRepositoryWrapper chargeRepositoryWrapper; private final BusinessEventNotifierService businessEventNotifierService; - public LoanDTO populateLoanDtoFromMap(final Map accountingBridgeData, final boolean cashBasedAccountingEnabled, - final boolean upfrontAccrualBasedAccountingEnabled, final boolean periodicAccrualBasedAccountingEnabled) { - final Long loanId = (Long) accountingBridgeData.get("loanId"); - final Long loanProductId = (Long) accountingBridgeData.get("loanProductId"); - final Long officeId = (Long) accountingBridgeData.get("officeId"); - final String currencyCode = (String) accountingBridgeData.get("currencyCode"); + public LoanDTO populateLoanDtoFromDTO( + final org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO accountingBridgeData) { + final Long loanId = accountingBridgeData.getLoanId(); + final Long loanProductId = accountingBridgeData.getLoanProductId(); + final Long officeId = accountingBridgeData.getOfficeId(); + final String currencyCode = accountingBridgeData.getCurrencyCode(); final List newLoanTransactions = new ArrayList<>(); - boolean isAccountTransfer = (Boolean) accountingBridgeData.get("isAccountTransfer"); - boolean isLoanMarkedAsChargeOff = (Boolean) accountingBridgeData.get("isChargeOff"); - boolean isLoanMarkedAsFraud = (Boolean) accountingBridgeData.get("isFraud"); - final Long chargeOffReasonCodeValue = (Long) accountingBridgeData.get("chargeOffReasonCodeValue"); + boolean isAccountTransfer = accountingBridgeData.isAccountTransfer(); + boolean isLoanMarkedAsChargeOff = accountingBridgeData.isChargeOff(); + boolean isLoanMarkedAsFraud = accountingBridgeData.isFraud(); + final Long chargeOffReasonCodeValue = accountingBridgeData.getChargeOffReasonCodeValue(); + final boolean cashBasedAccountingEnabled = accountingBridgeData.isCashBasedAccountingEnabled(); + final boolean upfrontAccrualBasedAccountingEnabled = accountingBridgeData.isUpfrontAccrualBasedAccountingEnabled(); + final boolean periodicAccrualBasedAccountingEnabled = accountingBridgeData.isPeriodicAccrualBasedAccountingEnabled(); - @SuppressWarnings("unchecked") - final List> newTransactionsMap = (List>) accountingBridgeData.get("newLoanTransactions"); + final List loanTransactionDTOs = accountingBridgeData.getNewLoanTransactions(); - for (final Map map : newTransactionsMap) { - final Long transactionOfficeId = (Long) map.get("officeId"); - final String transactionId = ((Long) map.get("id")).toString(); - final LocalDate transactionDate = ((LocalDate) map.get("date")); - final LoanTransactionEnumData transactionType = (LoanTransactionEnumData) map.get("type"); - final BigDecimal amount = (BigDecimal) map.get("amount"); - final BigDecimal principal = (BigDecimal) map.get("principalPortion"); - final BigDecimal interest = (BigDecimal) map.get("interestPortion"); - final BigDecimal fees = (BigDecimal) map.get("feeChargesPortion"); - final BigDecimal penalties = (BigDecimal) map.get("penaltyChargesPortion"); - final BigDecimal overPayments = (BigDecimal) map.get("overPaymentPortion"); - final boolean reversed = (Boolean) map.get("reversed"); - final Long paymentTypeId = (Long) map.get("paymentTypeId"); - final String chargeRefundChargeType = (String) map.get("chargeRefundChargeType"); - final LoanChargeData loanChargeData = (LoanChargeData) map.get("loanChargeData"); + for (final AccountingBridgeLoanTransactionDTO loanTxnDto : loanTransactionDTOs) { + final Long transactionOfficeId = loanTxnDto.getOfficeId(); + final String transactionId = loanTxnDto.getId().toString(); + final LocalDate transactionDate = loanTxnDto.getDate(); + final LoanTransactionEnumData transactionType = loanTxnDto.getType(); + final BigDecimal amount = loanTxnDto.getAmount(); + final BigDecimal principal = loanTxnDto.getPrincipalPortion(); + final BigDecimal interest = loanTxnDto.getInterestPortion(); + final BigDecimal fees = loanTxnDto.getFeeChargesPortion(); + final BigDecimal penalties = loanTxnDto.getPenaltyChargesPortion(); + final BigDecimal overPayments = loanTxnDto.getOverPaymentPortion(); + final boolean reversed = loanTxnDto.isReversed(); + final Long paymentTypeId = loanTxnDto.getPaymentTypeId(); + final String chargeRefundChargeType = loanTxnDto.getChargeRefundChargeType(); + final LoanChargeData loanChargeData = loanTxnDto.getLoanChargeData(); final List feePaymentDetails = new ArrayList<>(); final List penaltyPaymentDetails = new ArrayList<>(); // extract charge payment details (if exists) - if (map.containsKey("loanChargesPaid")) { - @SuppressWarnings("unchecked") - final List> loanChargesPaidData = (List>) map.get("loanChargesPaid"); - for (final Map loanChargePaid : loanChargesPaidData) { - final Long chargeId = (Long) loanChargePaid.get("chargeId"); - final Long loanChargeId = (Long) loanChargePaid.get("loanChargeId"); - final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty"); - final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); + if (loanTxnDto.getLoanChargesPaid() != null) { + List loanChargesPaidData = loanTxnDto.getLoanChargesPaid(); + for (final LoanChargePaidByDTO loanChargePaid : loanChargesPaidData) { + final Long chargeId = loanChargePaid.getChargeId(); + final Long loanChargeId = loanChargePaid.getLoanChargeId(); + final boolean isPenalty = loanChargePaid.getIsPenalty(); + final BigDecimal chargeAmountPaid = loanChargePaid.getAmount(); final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, chargeAmountPaid, loanChargeId); if (isPenalty) { penaltyPaymentDetails.add(chargePaymentDTO); @@ -150,23 +147,23 @@ public class AccountingProcessorHelper { } } - if (!isAccountTransfer) { - isAccountTransfer = this.accountTransfersReadPlatformService.isAccountTransfer(Long.parseLong(transactionId), + boolean localIsAccountTransfer = isAccountTransfer; + if (!localIsAccountTransfer) { + localIsAccountTransfer = this.accountTransfersReadPlatformService.isAccountTransfer(Long.parseLong(transactionId), PortfolioAccountType.LOAN); } - BigDecimal principalPaid = (BigDecimal) map.get("principalPaid"); - BigDecimal feePaid = (BigDecimal) map.get("feePaid"); - BigDecimal penaltyPaid = (BigDecimal) map.get("penaltyPaid"); + BigDecimal principalPaid = loanTxnDto.getPrincipalPaid(); + BigDecimal feePaid = loanTxnDto.getFeePaid(); + BigDecimal penaltyPaid = loanTxnDto.getPenaltyPaid(); final LoanTransactionDTO transaction = new LoanTransactionDTO(transactionOfficeId, paymentTypeId, transactionId, transactionDate, transactionType, amount, principal, interest, fees, penalties, overPayments, reversed, - penaltyPaymentDetails, feePaymentDetails, isAccountTransfer, chargeRefundChargeType, loanChargeData, principalPaid, + penaltyPaymentDetails, feePaymentDetails, localIsAccountTransfer, chargeRefundChargeType, loanChargeData, principalPaid, feePaid, penaltyPaid); - Boolean isLoanToLoanTransfer = (Boolean) accountingBridgeData.get("isLoanToLoanTransfer"); - transaction.setLoanToLoanTransfer(isLoanToLoanTransfer != null && isLoanToLoanTransfer); - newLoanTransactions.add(transaction); + transaction.setLoanToLoanTransfer(loanTxnDto.isLoanToLoanTransfer()); + newLoanTransactions.add(transaction); } return new LoanDTO(loanId, loanProductId, officeId, currencyCode, cashBasedAccountingEnabled, upfrontAccrualBasedAccountingEnabled, @@ -559,10 +556,10 @@ public class AccountingProcessorHelper { } public void createAccrualBasedJournalEntriesAndReversalsForSavingsTax(final Office office, final String currencyCode, - final AccrualAccountsForSavings accountTypeToBeDebited, final AccrualAccountsForSavings accountTypeToBeCredited, - final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, - final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal, final List taxDetails) { - + final AccountingConstants.AccrualAccountsForSavings accountTypeToBeDebited, + final AccountingConstants.AccrualAccountsForSavings accountTypeToBeCredited, final Long savingsProductId, + final Long paymentTypeId, final Long savingsId, final String transactionId, final LocalDate transactionDate, + final BigDecimal amount, final Boolean isReversal, final List taxDetails) { for (TaxPaymentDTO taxPaymentDTO : taxDetails) { if (taxPaymentDTO.getAmount() != null) { if (taxPaymentDTO.getCreditAccountId() == null) { @@ -769,10 +766,10 @@ public class AccountingProcessorHelper { } public void createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(final Office office, final String currencyCode, - final AccrualAccountsForSavings accountTypeToBeDebited, AccrualAccountsForSavings accountTypeToBeCredited, - final Long savingsProductId, final Long paymentTypeId, final Long loanId, final String transactionId, - final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal, - final List chargePaymentDTOs) { + final AccountingConstants.AccrualAccountsForSavings accountTypeToBeDebited, + final AccountingConstants.AccrualAccountsForSavings accountTypeToBeCredited, final Long savingsProductId, + final Long paymentTypeId, final Long loanId, final String transactionId, final LocalDate transactionDate, + final BigDecimal totalAmount, final Boolean isReversal, final List chargePaymentDTOs) { // TODO Vishwas: Remove this validation, as and when appropriate Junit // tests are written for accounting /** diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java index 3671ffa8c..6f3c96fed 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java @@ -24,6 +24,7 @@ import java.util.Map; import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; public interface JournalEntryWritePlatformService { @@ -31,7 +32,7 @@ public interface JournalEntryWritePlatformService { CommandProcessingResult revertJournalEntry(JsonCommand command); - void createJournalEntriesForLoan(Map accountingBridgeData); + void createJournalEntriesForLoan(AccountingBridgeDataDTO accountingBridgeData); void createJournalEntriesForSavings(Map accountingBridgeData); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index a0c7d9e04..1aec81280 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -75,6 +75,7 @@ import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepo import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; import org.apache.fineract.portfolio.PortfolioProductType; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.apache.fineract.useradministration.domain.AppUser; @@ -480,15 +481,13 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa @Transactional @Override - public void createJournalEntriesForLoan(final Map accountingBridgeData) { - - final boolean cashBasedAccountingEnabled = (Boolean) accountingBridgeData.get("cashBasedAccountingEnabled"); - final boolean upfrontAccrualBasedAccountingEnabled = (Boolean) accountingBridgeData.get("upfrontAccrualBasedAccountingEnabled"); - final boolean periodicAccrualBasedAccountingEnabled = (Boolean) accountingBridgeData.get("periodicAccrualBasedAccountingEnabled"); + public void createJournalEntriesForLoan(final AccountingBridgeDataDTO accountingBridgeData) { + final boolean cashBasedAccountingEnabled = accountingBridgeData.isCashBasedAccountingEnabled(); + final boolean upfrontAccrualBasedAccountingEnabled = accountingBridgeData.isUpfrontAccrualBasedAccountingEnabled(); + final boolean periodicAccrualBasedAccountingEnabled = accountingBridgeData.isPeriodicAccrualBasedAccountingEnabled(); if (cashBasedAccountingEnabled || upfrontAccrualBasedAccountingEnabled || periodicAccrualBasedAccountingEnabled) { - final LoanDTO loanDTO = this.helper.populateLoanDtoFromMap(accountingBridgeData, cashBasedAccountingEnabled, - upfrontAccrualBasedAccountingEnabled, periodicAccrualBasedAccountingEnabled); + final LoanDTO loanDTO = this.helper.populateLoanDtoFromDTO(accountingBridgeData); final AccountingProcessorForLoan accountingProcessorForLoan = this.accountingProcessorForLoanFactory .determineProcessor(loanDTO); accountingProcessorForLoan.createJournalEntriesForLoan(loanDTO); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java index aef8bbb8b..c22fbf394 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java @@ -46,10 +46,7 @@ import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; -import org.apache.fineract.portfolio.client.domain.ClientTransactionRepository; -import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -63,14 +60,11 @@ public class AccountingJournalEntryConfiguration { public AccountingProcessorHelper accountingProcessorHelper(JournalEntryRepository glJournalEntryRepository, ProductToGLAccountMappingRepository accountMappingRepository, FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository, GLClosureRepository closureRepository, - GLAccountRepository glAccountRepository, OfficeRepository officeRepository, LoanTransactionRepository loanTransactionRepository, - ClientTransactionRepository clientTransactionRepository, - SavingsAccountTransactionRepository savingsAccountTransactionRepository, + GLAccountRepository glAccountRepository, OfficeRepository officeRepository, AccountTransfersReadPlatformService accountTransfersReadPlatformService, ChargeRepositoryWrapper chargeRepositoryWrapper, BusinessEventNotifierService businessEventNotifierService) { return new AccountingProcessorHelper(glJournalEntryRepository, accountMappingRepository, financialActivityAccountRepository, - closureRepository, glAccountRepository, officeRepository, loanTransactionRepository, clientTransactionRepository, - savingsAccountTransactionRepository, accountTransfersReadPlatformService, chargeRepositoryWrapper, + closureRepository, glAccountRepository, officeRepository, accountTransfersReadPlatformService, chargeRepositoryWrapper, businessEventNotifierService); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 21ac0cced..439d7a743 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -87,6 +87,7 @@ import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatfor import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData; import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData; @@ -94,6 +95,7 @@ import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanDownPaymentTransactionValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanForeclosureValidator; @@ -157,6 +159,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final LoanRefundService loanRefundService; private final LoanAccountService loanAccountService; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final LoanAccountingBridgeMapper loanAccountingBridgeMapper; @Transactional @Override @@ -449,19 +452,26 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final MonetaryCurrency currency = loanAccount.getCurrency(); - List> accountingBridgeData = new ArrayList<>(); if (loanAccount.isChargedOff()) { - accountingBridgeData = loanAccount.deriveAccountingBridgeDataForChargeOff(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); - } else { - accountingBridgeData.add(loanAccount.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer)); - } - for (Map accountingData : accountingBridgeData) { - accountingData.put("isLoanToLoanTransfer", isLoanToLoanTransfer); - this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingData); - } + final List accountingBridgeDataList = loanAccountingBridgeMapper + .deriveAccountingBridgeDataForChargeOff(currency.getCode(), existingTransactionIds, existingReversedTransactionIds, + isAccountTransfer, loanAccount); + if (isLoanToLoanTransfer) { + accountingBridgeDataList.forEach(dto -> dto.getNewLoanTransactions().forEach(tx -> tx.setLoanToLoanTransfer(true))); + } + for (AccountingBridgeDataDTO accountingBridgeData : accountingBridgeDataList) { + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + } + } else { + final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loanAccount); + if (isLoanToLoanTransfer) { + accountingBridgeData.getNewLoanTransactions().forEach(tx -> tx.setLoanToLoanTransfer(true)); + } + + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + } } private void checkClientOrGroupActive(final Loan loan) { @@ -985,7 +995,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final List newInstallments = new ArrayList<>(loan.getRepaymentScheduleInstallments()); final MonetaryCurrency currency = loan.getCurrency(); Money totalPrincipal = Money.zero(currency); - final Money[] balances = loan.retriveIncomeForOverlappingPeriod(transactionDate); + final Money[] balances = loan.retrieveIncomeForOverlappingPeriod(transactionDate); boolean isInterestComponent = true; for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { if (!DateUtils.isAfter(transactionDate, installment.getDueDate())) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java index 76cb3eb1a..ee14d9ce4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java @@ -42,6 +42,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanTermVariationsMapper; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; @@ -59,6 +60,7 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet { private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanUtilService loanUtilService; private final BusinessEventNotifierService businessEventNotifierService; + private final LoanTermVariationsMapper loanTermVariationsMapper; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { @@ -120,7 +122,8 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet { private void adjustRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adjustedRescheduleToDate) { final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, holiday.getFromDate()); - final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + final LoanApplicationTerms loanApplicationTerms = loanTermVariationsMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, + loan); // first repayment's from date is same as disbursement date. LocalDate tmpFromDate = loan.getDisbursementDate(); @@ -152,7 +155,8 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet { private void adjustAllRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adjustedRescheduleToDate) { final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, holiday.getFromDate()); - final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + final LoanApplicationTerms loanApplicationTerms = loanTermVariationsMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, + loan); // first repayment's from date is same as disbursement date. LocalDate tmpFromDate = loan.getDisbursementDate(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index 077b32c0e..440a42c6f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -44,6 +44,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleD import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanTermVariationsMapper; import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanScheduleValidator; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; @@ -63,6 +64,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC private final LoanUtilService loanUtilService; private final LoanRepositoryWrapper loanRepository; private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; + private final LoanTermVariationsMapper loanTermVariationsMapper; @Override public LoanScheduleModel calculateLoanSchedule(final JsonQuery query, Boolean validateParams) { @@ -204,7 +206,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC private LoanApplicationTerms constructLoanApplicationTerms(final Loan loan) { final LocalDate recalculateFrom = null; ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); - return loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + return loanTermVariationsMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, loan); } private Loan fetchLoan(final Long accountId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index 9334fb7cc..6ef7855fe 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -23,6 +23,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; @@ -38,32 +39,23 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplica import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanTermVariationsMapper; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class LoanReschedulePreviewPlatformServiceImpl implements LoanReschedulePreviewPlatformService { + private static final DefaultScheduledDateGenerator DEFAULT_SCHEDULED_DATE_GENERATOR = new DefaultScheduledDateGenerator(); private final LoanRescheduleRequestRepositoryWrapper loanRescheduleRequestRepository; private final LoanUtilService loanUtilService; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanScheduleGeneratorFactory loanScheduleFactory; - private static final DefaultScheduledDateGenerator DEFAULT_SCHEDULED_DATE_GENERATOR = new DefaultScheduledDateGenerator(); - - @Autowired - public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepositoryWrapper loanRescheduleRequestRepository, - final LoanUtilService loanUtilService, - final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, - final LoanScheduleGeneratorFactory loanScheduleFactory) { - this.loanRescheduleRequestRepository = loanRescheduleRequestRepository; - this.loanUtilService = loanUtilService; - this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; - this.loanScheduleFactory = loanScheduleFactory; - } + private final LoanTermVariationsMapper loanTermVariationsMapper; @Override public LoanScheduleModel previewLoanReschedule(Long requestId) { @@ -80,7 +72,8 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP loanRescheduleRequest.getRescheduleFromDate()); LocalDate rescheduleFromDate = null; List removeLoanTermVariationsData = new ArrayList<>(); - final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + final LoanApplicationTerms loanApplicationTerms = loanTermVariationsMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, + loan); LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists(); if (dueDateVariationInCurrentRequest != null) { for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 03c156be4..0a52c8fd1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -68,6 +69,8 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepayme import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanTermVariationsMapper; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; @@ -116,6 +119,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche private final LoanAccrualsProcessingService loanAccrualsProcessingService; private final LoanChargeService loanChargeService; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final LoanTermVariationsMapper loanTermVariationsMapper; + private final LoanAccountingBridgeMapper loanAccountingBridgeMapper; private final LoanScheduleComponent loanSchedule; /** @@ -363,7 +368,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche Collection loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); - final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + final LoanApplicationTerms loanApplicationTerms = loanTermVariationsMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, + loan); LocalDate rescheduleFromDate = null; List activeLoanTermVariations = loan.getActiveLoanTermVariations(); @@ -403,7 +409,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche } BigDecimal annualNominalInterestRate = null; List loanTermVariations = new ArrayList<>(); - loan.constructLoanTermVariations(scheduleGeneratorDTO.getFloatingRateDTO(), annualNominalInterestRate, loanTermVariations); + loanTermVariationsMapper.constructLoanTermVariations(scheduleGeneratorDTO.getFloatingRateDTO(), annualNominalInterestRate, + loanTermVariations, loan); loanApplicationTerms.getLoanTermVariations().setExceptionData(loanTermVariations); /* @@ -495,8 +502,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche private void postJournalEntries(Loan loan, List existingTransactionIds, List existingReversedTransactionIds) { final MonetaryCurrency currency = loan.getCurrency(); boolean isAccountTransfer = false; - final Map accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); + final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan); this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java index a89681b28..147021029 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java @@ -28,6 +28,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -90,6 +91,7 @@ import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException; import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; +import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; @@ -109,6 +111,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementData import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; @@ -200,6 +203,7 @@ public final class LoanApplicationValidator { private final CalendarInstanceRepository calendarInstanceRepository; private final LoanUtilService loanUtilService; private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService; + private final LoanMapper loanMapper; public void validateForCreate(final Loan loan) { final LocalDate expectedFirstRepaymentOnDate = loan.getExpectedFirstRepaymentOnDate(); @@ -1404,12 +1408,11 @@ public final class LoanApplicationValidator { BigDecimal loanOutstanding = this.loanReadPlatformService .retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate) .getAmount(); - final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount(); + final BigDecimal firstDisbursalAmount = getFirstDisbursalAmount(loan); if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) { throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", "Topup loan amount should be greater than outstanding amount of loan to be closed."); } - } } @@ -1953,7 +1956,7 @@ public final class LoanApplicationValidator { this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS); } - public void validateTopupLoan(Loan loan, LocalDate expectedDisbursementDate) { + public BigDecimal validateTopupLoan(final Loan loan, final LocalDate disbursementDate) { final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose(); final Loan loanToClose = loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, loan.getClientId()); if (loanToClose == null) { @@ -1969,16 +1972,15 @@ public final class LoanApplicationValidator { + " should be after last transaction date of loan to be closed " + lastUserTransactionOnLoanToClose); } - BigDecimal loanOutstanding = loanReadPlatformService - .retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate).getAmount(); - final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount(); + final BigDecimal loanOutstanding = loanReadPlatformService + .retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, disbursementDate).getAmount(); + final BigDecimal firstDisbursalAmount = getFirstDisbursalAmount(loan); if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) { throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", "Topup loan amount should be greater than outstanding amount of loan to be closed."); } - BigDecimal netDisbursalAmount = loan.getApprovedPrincipal().subtract(loanOutstanding); - loan.adjustNetDisbursalAmount(netDisbursalAmount); + return loanOutstanding; } public void validateApproval(JsonCommand command, Long loanId) { @@ -2074,7 +2076,9 @@ public final class LoanApplicationValidator { StatusEnum.APPROVE.getValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId()); if (loan.isTopup() && loan.getClientId() != null) { - validateTopupLoan(loan, expectedDisbursementDate); + final BigDecimal loanOutstanding = validateTopupLoan(loan, expectedDisbursementDate); + final BigDecimal netDisbursalAmountAdjusted = loan.getApprovedPrincipal().subtract(loanOutstanding); + loan.adjustNetDisbursalAmount(netDisbursalAmountAdjusted); } if (!loan.getStatus().isSubmittedAndPendingApproval()) { @@ -2202,4 +2206,18 @@ public final class LoanApplicationValidator { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); } + + private BigDecimal getFirstDisbursalAmount(final Loan loan) { + BigDecimal firstDisbursalAmount; + + if (loan.isMultiDisburmentLoan()) { + List disbursementData = loanMapper.getDisbursementData(loan); + Collections.sort(disbursementData); + firstDisbursalAmount = disbursementData.get(disbursementData.size() - 1).getPrincipal(); + } else { + firstDisbursalAmount = loan.getLoanRepaymentScheduleDetail().getPrincipal().getAmount(); + } + return firstDisbursalAmount; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java index 693933f9d..492cb99a4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java @@ -32,7 +32,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -60,6 +59,8 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO; import org.apache.fineract.portfolio.loanaccount.data.AccrualBalances; import org.apache.fineract.portfolio.loanaccount.data.AccrualChargeData; import org.apache.fineract.portfolio.loanaccount.data.AccrualPeriodData; @@ -79,6 +80,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepayme import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.springframework.beans.factory.annotation.Qualifier; @@ -109,6 +111,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing @Qualifier(TaskExecutorConstant.CONFIGURABLE_TASK_EXECUTOR_BEAN_NAME) private final ThreadPoolTaskExecutor taskExecutor; private final TransactionTemplate transactionTemplate; + private final LoanAccountingBridgeMapper loanAccountingBridgeMapper; /** * method adds accrual for batch job "Add Periodic Accrual Transactions" and add accruals api for Loan @@ -413,7 +416,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing if (!isFinal || progressiveAccrual) { loan.setAccruedTill(isFinal ? accrualDate : tillDate); } - ArrayList> newTransactionMapping = new ArrayList<>(); + List newTransactionDTOs = new ArrayList<>(); for (LoanTransaction accrualTransaction : accrualTransactions) { accrualTransaction = loanTransactionRepository.saveAndFlush(accrualTransaction); if (addJournal) { @@ -421,11 +424,17 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing ? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction) : new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction); businessEventNotifierService.notifyPostBusinessEvent(businessEvent); - newTransactionMapping.add(accrualTransaction.toMapData(currency.getCode())); + AccountingBridgeLoanTransactionDTO transactionDTO = loanAccountingBridgeMapper.mapToLoanTransactionData(accrualTransaction, + currency.getCode()); + newTransactionDTOs.add(transactionDTO); } } if (addJournal) { - Map accountingBridgeData = deriveAccountingBridgeData(loan, newTransactionMapping); + AccountingBridgeDataDTO accountingBridgeData = new AccountingBridgeDataDTO(loan.getId(), loan.getLoanProduct().getId(), + loan.getOfficeId(), loan.getCurrencyCode(), loan.getSummary().getTotalInterestCharged(), + loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct(), + loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(), false, + false, false, null, newTransactionDTOs); this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); } } @@ -1110,30 +1119,12 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing } } - // Utility - - private Map deriveAccountingBridgeData(@NotNull Loan loan, List> newLoanTransactions) { - final Map accountingBridgeData = new LinkedHashMap<>(); - accountingBridgeData.put("loanId", loan.getId()); - accountingBridgeData.put("loanProductId", loan.getLoanProduct().getId()); - accountingBridgeData.put("officeId", loan.getOfficeId()); - accountingBridgeData.put("currencyCode", loan.getCurrencyCode()); - accountingBridgeData.put("cashBasedAccountingEnabled", loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()); - accountingBridgeData.put("upfrontAccrualBasedAccountingEnabled", loan.isUpfrontAccrualAccountingEnabledOnLoanProduct()); - accountingBridgeData.put("periodicAccrualBasedAccountingEnabled", loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()); - accountingBridgeData.put("isAccountTransfer", false); - accountingBridgeData.put("isChargeOff", false); - accountingBridgeData.put("isFraud", false); - accountingBridgeData.put("newLoanTransactions", newLoanTransactions); - return accountingBridgeData; - } - private void postJournalEntries(final Loan loan, final List existingTransactionIds, final List existingReversedTransactionIds) { final MonetaryCurrency currency = loan.getCurrency(); boolean isAccountTransfer = false; - final Map accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); + final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan); journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index d9b10d53c..a9aa3e76e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -90,6 +90,7 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -128,6 +129,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanSc import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanDownPaymentTransactionValidator; @@ -178,6 +180,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final LoanAccountService loanAccountService; private final LoanAdjustmentService loanAdjustmentService; + private final LoanAccountingBridgeMapper loanAccountingBridgeMapper; private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) { return DateUtils.isAfter(loanCharge.getDueDate(), e.getFromDate()) && !DateUtils.isAfter(loanCharge.getDueDate(), e.getDueDate()); @@ -929,8 +932,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); - final Map accountingBridgeData = loan.deriveAccountingBridgeData(loan.getCurrency().getCode(), - existingTransactionIds, existingReversedTransactionIds, false); + final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData( + loan.getCurrency().getCode(), existingTransactionIds, existingReversedTransactionIds, false, loan); this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate); @@ -1302,8 +1305,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo final MonetaryCurrency currency = loan.getCurrency(); boolean isAccountTransfer = false; - final Map accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); + final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan); this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java index b8b175f99..6e4460e89 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java @@ -24,15 +24,18 @@ import com.google.common.base.Splitter; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.ExternalId; @@ -76,14 +79,14 @@ public class LoanDisbursementService { if (disbursementDataArray != null && !disbursementDataArray.isEmpty()) { String dateFormat; Locale locale = null; - final Map dateAndLocale = loan.getDateFormatAndLocale(jsonCommand); + final Map dateAndLocale = getDateFormatAndLocale(jsonCommand); dateFormat = dateAndLocale.get(LoanApiConstants.dateFormatParameterName); if (dateAndLocale.containsKey(LoanApiConstants.localeParameterName)) { locale = JsonParserHelper.localeFromString(dateAndLocale.get(LoanApiConstants.localeParameterName)); } for (JsonElement jsonElement : disbursementDataArray) { final JsonObject jsonObject = jsonElement.getAsJsonObject(); - final Map parsedDisbursementData = loan.parseDisbursementDetails(jsonObject, dateFormat, locale); + final Map parsedDisbursementData = parseDisbursementDetails(jsonObject, dateFormat, locale); final LocalDate expectedDisbursementDate = (LocalDate) parsedDisbursementData .get(LoanApiConstants.expectedDisbursementDateParameterName); final BigDecimal principal = (BigDecimal) parsedDisbursementData @@ -298,4 +301,63 @@ public class LoanDisbursementService { }) // .forEach(loanCharge -> reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge)); } + + // This method returns date format and locale if present in the JsonCommand + private Map getDateFormatAndLocale(final JsonCommand jsonCommand) { + Map returnObject = new HashMap<>(); + JsonElement jsonElement = jsonCommand.parsedJson(); + if (jsonElement.isJsonObject()) { + JsonObject topLevel = jsonElement.getAsJsonObject(); + if (topLevel.has(LoanApiConstants.dateFormatParameterName) + && topLevel.get(LoanApiConstants.dateFormatParameterName).isJsonPrimitive()) { + final JsonPrimitive primitive = topLevel.get(LoanApiConstants.dateFormatParameterName).getAsJsonPrimitive(); + returnObject.put(LoanApiConstants.dateFormatParameterName, primitive.getAsString()); + } + if (topLevel.has(LoanApiConstants.localeParameterName) + && topLevel.get(LoanApiConstants.localeParameterName).isJsonPrimitive()) { + final JsonPrimitive primitive = topLevel.get(LoanApiConstants.localeParameterName).getAsJsonPrimitive(); + String localeString = primitive.getAsString(); + returnObject.put(LoanApiConstants.localeParameterName, localeString); + } + } + return returnObject; + } + + private Map parseDisbursementDetails(final JsonObject jsonObject, String dateFormat, Locale locale) { + Map returnObject = new HashMap<>(); + if (jsonObject.get(LoanApiConstants.expectedDisbursementDateParameterName) != null + && jsonObject.get(LoanApiConstants.expectedDisbursementDateParameterName).isJsonPrimitive()) { + final JsonPrimitive primitive = jsonObject.get(LoanApiConstants.expectedDisbursementDateParameterName).getAsJsonPrimitive(); + final String valueAsString = primitive.getAsString(); + if (StringUtils.isNotBlank(valueAsString)) { + LocalDate date = JsonParserHelper.convertFrom(valueAsString, LoanApiConstants.expectedDisbursementDateParameterName, + dateFormat, locale); + if (date != null) { + returnObject.put(LoanApiConstants.expectedDisbursementDateParameterName, date); + } + } + } + + if (jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive() + && StringUtils.isNotBlank(jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString())) { + BigDecimal principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); + returnObject.put(LoanApiConstants.disbursementPrincipalParameterName, principal); + } + + if (jsonObject.has(LoanApiConstants.disbursementIdParameterName) + && jsonObject.get(LoanApiConstants.disbursementIdParameterName).isJsonPrimitive() + && StringUtils.isNotBlank(jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString())) { + Long id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong(); + returnObject.put(LoanApiConstants.disbursementIdParameterName, id); + } + + if (jsonObject.has(LoanApiConstants.loanChargeIdParameterName) + && jsonObject.get(LoanApiConstants.loanChargeIdParameterName).isJsonPrimitive() + && StringUtils.isNotBlank(jsonObject.get(LoanApiConstants.loanChargeIdParameterName).getAsString())) { + returnObject.put(LoanApiConstants.loanChargeIdParameterName, + jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString()); + } + return returnObject; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanJournalEntryPoster.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanJournalEntryPoster.java index b1b41291b..543042eaa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanJournalEntryPoster.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanJournalEntryPoster.java @@ -18,13 +18,13 @@ */ package org.apache.fineract.portfolio.loanaccount.service; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; import org.springframework.stereotype.Component; @Component @@ -32,23 +32,24 @@ import org.springframework.stereotype.Component; public class LoanJournalEntryPoster { private final JournalEntryWritePlatformService journalEntryWritePlatformService; + private final LoanAccountingBridgeMapper loanAccountingBridgeMapper; public void postJournalEntries(final Loan loan, final List existingTransactionIds, final List existingReversedTransactionIds) { - final MonetaryCurrency currency = loan.getCurrency(); boolean isAccountTransfer = false; - List> accountingBridgeData = new ArrayList<>(); - if (loan.isChargedOff()) { - accountingBridgeData = loan.deriveAccountingBridgeDataForChargeOff(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); - } else { - accountingBridgeData.add(loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer)); - } - for (Map accountingData : accountingBridgeData) { - this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingData); - } + if (loan.isChargedOff()) { + List accountingBridgeDataList = loanAccountingBridgeMapper.deriveAccountingBridgeDataForChargeOff( + currency.getCode(), existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan); + for (AccountingBridgeDataDTO accountingBridgeData : accountingBridgeDataList) { + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + } + } else { + AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan); + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + } } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 63a7f29bb..f5f61cdf2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -38,7 +38,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; @@ -141,15 +141,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -@AllArgsConstructor +@RequiredArgsConstructor @Transactional(readOnly = true) public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, LoanReadPlatformServiceCommon { - private static final String ACCRUAL_ON_CHARGE_SUBMITTED_ON_DATE = "submitted-date"; private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; private final LoanRepositoryWrapper loanRepositoryWrapper; @@ -164,7 +162,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa private final CalendarReadPlatformService calendarReadPlatformService; private final StaffReadPlatformService staffReadPlatformService; private final PaginationHelper paginationHelper; - private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; @@ -179,6 +176,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa private final LoanTransactionRelationReadService loanTransactionRelationReadService; private final LoanForeclosureValidator loanForeclosureValidator; private final LoanTransactionMapper loanTransactionMapper; + private final org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper loanMapper; private final LoanTransactionProcessingService loadTransactionProcessingService; @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index aa5551219..a29bac9bc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -149,6 +149,7 @@ import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.command.LoanUpdateCommand; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; @@ -163,7 +164,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachin import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; @@ -195,6 +195,8 @@ import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDoma import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; @@ -247,14 +249,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final FromJsonHelper fromApiJsonHelper; private final CalendarRepository calendarRepository; private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; - private final LoanApplicationValidator loanApplicationCommandFromApiJsonHelper; + private final LoanApplicationValidator loanApplicationValidator; private final AccountAssociationsRepository accountAssociationRepository; private final AccountTransferDetailRepository accountTransferDetailRepository; private final BusinessEventNotifierService businessEventNotifierService; private final GuarantorDomainService guarantorDomainService; private final LoanUtilService loanUtilService; private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService; - private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy; private final CodeValueRepositoryWrapper codeValueRepository; private final CashierTransactionDataValidator cashierTransactionDataValidator; private final GLIMAccountInfoRepository glimRepository; @@ -280,6 +281,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final LoanAccountService loanAccountService; private final LoanJournalEntryPoster journalEntryPoster; private final LoanAdjustmentService loanAdjustmentService; + private final LoanAccountingBridgeMapper loanAccountingBridgeMapper; + private final LoanMapper loanMapper; private final LoanTransactionProcessingService loanTransactionProcessingService; @Transactional @@ -378,27 +381,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName); if (loan.isTopup() && loan.getClientId() != null) { - final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose(); - final Loan loanToClose = this.loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, loan.getClientId()); - if (loanToClose == null) { - throw new GeneralPlatformDomainRuleException("error.msg.loan.to.be.closed.with.topup.is.not.active", - "Loan to be closed with this topup is not active."); - } - final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate(); - if (DateUtils.isBefore(loan.getDisbursementDate(), lastUserTransactionOnLoanToClose)) { - throw new GeneralPlatformDomainRuleException( - "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed", - "Disbursal date of this loan application " + loan.getDisbursementDate() - + " should be after last transaction date of loan to be closed " + lastUserTransactionOnLoanToClose); - } - - BigDecimal loanOutstanding = this.loanReadPlatformService - .retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, actualDisbursementDate).getAmount(); - final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount(); - if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) { - throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", - "Topup loan amount should be greater than outstanding amount of loan to be closed."); - } + final BigDecimal loanOutstanding = loanApplicationValidator.validateTopupLoan(loan, actualDisbursementDate); amountToDisburse = disburseAmount.minus(loanOutstanding); disburseLoanToLoan(loan, command, loanOutstanding); @@ -625,7 +608,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private void createAndSaveLoanScheduleArchive(final Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO) { LoanRescheduleRequest loanRescheduleRequest = null; - LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO); + LoanScheduleModel loanScheduleModel = loanMapper.regenerateScheduleModel(scheduleGeneratorDTO, loan); List installments = retrieveRepaymentScheduleFromModel(loanScheduleModel); this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(installments, loan, loanRescheduleRequest); } @@ -922,11 +905,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN); - String noteText; createNote(loan, command, changes); boolean isAccountTransfer = false; - final Map accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); + final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan); journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoDisbursalBusinessEvent(loan)); @@ -2353,8 +2335,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element); - loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, - principal); + loanApplicationValidator.validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -2791,8 +2772,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); createNote(loan, command, changes); boolean isAccountTransfer = false; - final Map accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); + final AccountingBridgeDataDTO accountingBridgeData = loanAccountingBridgeMapper.deriveAccountingBridgeData(currency.getCode(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, loan); journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoLastDisbursalBusinessEvent(loan)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java index cf957cd36..28a8fc219 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -83,8 +83,10 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository; import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionValidator; @@ -162,7 +164,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @Configuration public class LoanAccountConfiguration { @@ -302,7 +303,8 @@ public class LoanAccountConfiguration { LoanAccrualsProcessingService loanAccrualsProcessingService, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanChargeValidator loanChargeValidator, LoanScheduleService loanScheduleService, ReprocessLoanTransactionsService reprocessLoanTransactionsService, - LoanAccountService loanAccountService, LoanAdjustmentService loanAdjustmentService) { + LoanAccountService loanAccountService, LoanAdjustmentService loanAdjustmentService, + LoanAccountingBridgeMapper loanAccountingBridgeMapper) { return new LoanChargeWritePlatformServiceImpl(loanChargeApiJsonValidator, loanAssembler, chargeRepository, businessEventNotifierService, loanTransactionRepository, accountTransfersWritePlatformService, loanRepositoryWrapper, journalEntryWritePlatformService, loanAccountDomainService, loanChargeRepository, loanWritePlatformService, loanUtilService, @@ -310,7 +312,8 @@ public class LoanAccountConfiguration { configurationDomainService, loanRepaymentScheduleTransactionProcessorFactory, externalIdFactory, accountTransferDetailRepository, loanChargeAssembler, paymentDetailWritePlatformService, noteRepository, loanAccrualTransactionBusinessEventService, loanAccrualsProcessingService, loanDownPaymentTransactionValidator, - loanChargeValidator, loanScheduleService, reprocessLoanTransactionsService, loanAccountService, loanAdjustmentService); + loanChargeValidator, loanScheduleService, reprocessLoanTransactionsService, loanAccountService, loanAdjustmentService, + loanAccountingBridgeMapper); } @Bean @@ -322,23 +325,23 @@ public class LoanAccountConfiguration { FundReadPlatformService fundReadPlatformService, ChargeReadPlatformService chargeReadPlatformService, CodeValueReadPlatformService codeValueReadPlatformService, CalendarReadPlatformService calendarReadPlatformService, StaffReadPlatformService staffReadPlatformService, PaginationHelper paginationHelper, - NamedParameterJdbcTemplate namedParameterJdbcTemplate, PaymentTypeReadPlatformService paymentTypeReadPlatformService, + PaymentTypeReadPlatformService paymentTypeReadPlatformService, LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, FloatingRatesReadPlatformService floatingRatesReadPlatformService, LoanUtilService loanUtilService, ConfigurationDomainService configurationDomainService, AccountDetailsReadPlatformService accountDetailsReadPlatformService, ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository, LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService, - LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper, + LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper, LoanMapper loanMapper, LoanTransactionProcessingService loanTransactionProcessingService) { return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository, loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService, fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService, - staffReadPlatformService, paginationHelper, namedParameterJdbcTemplate, paymentTypeReadPlatformService, + staffReadPlatformService, paginationHelper, paymentTypeReadPlatformService, loanRepaymentScheduleTransactionProcessorFactory, floatingRatesReadPlatformService, loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator, delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService, - loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService); + loanForeclosureValidator, loanTransactionMapper, loanMapper, loanTransactionProcessingService); } @Bean @@ -387,9 +390,9 @@ public class LoanAccountConfiguration { AccountTransferDetailRepository accountTransferDetailRepository, BusinessEventNotifierService businessEventNotifierService, GuarantorDomainService guarantorDomainService, LoanUtilService loanUtilService, EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, - LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, CodeValueRepositoryWrapper codeValueRepository, - CashierTransactionDataValidator cashierTransactionDataValidator, GLIMAccountInfoRepository glimRepository, - LoanRepository loanRepository, RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler, + CodeValueRepositoryWrapper codeValueRepository, CashierTransactionDataValidator cashierTransactionDataValidator, + GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, + RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler, PostDatedChecksRepository postDatedChecksRepository, LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository, LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService, @@ -401,6 +404,7 @@ public class LoanAccountConfiguration { LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService, ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanAccountService loanAccountService, LoanJournalEntryPoster journalEntryPoster, LoanAdjustmentService loanAdjustmentService, + LoanAccountingBridgeMapper loanAccountingBridgeMapper, LoanMapper loanMapper, LoanTransactionProcessingService loanTransactionProcessingService) { return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository, @@ -409,14 +413,14 @@ public class LoanAccountConfiguration { accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService, loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService, loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService, - guarantorDomainService, loanUtilService, entityDatatableChecksWritePlatformService, transactionProcessingStrategy, - codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler, + guarantorDomainService, loanUtilService, entityDatatableChecksWritePlatformService, codeValueRepository, + cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator, loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, - reprocessLoanTransactionsService, loanAccountService, journalEntryPoster, loanAdjustmentService, - loanTransactionProcessingService); + reprocessLoanTransactionsService, loanAccountService, journalEntryPoster, loanAdjustmentService, loanAccountingBridgeMapper, + loanMapper, loanTransactionProcessingService); } @Bean @@ -461,10 +465,11 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(LoanScheduleService.class) - public LoanScheduleService loanScheduleService(LoanChargeService loanChargeService, - ReprocessLoanTransactionsService reprocessLoanTransactionsService, - LoanTransactionProcessingService loanTransactionProcessingService, LoanScheduleComponent loanSchedule) { - return new LoanScheduleService(loanChargeService, reprocessLoanTransactionsService, loanTransactionProcessingService, loanSchedule); + public LoanScheduleService loanScheduleService(final LoanChargeService loanChargeService, + final ReprocessLoanTransactionsService reprocessLoanTransactionsService, final LoanMapper loanMapper, + final LoanTransactionProcessingService loanTransactionProcessingService, LoanScheduleComponent loanSchedule) { + return new LoanScheduleService(loanChargeService, reprocessLoanTransactionsService, loanMapper, loanTransactionProcessingService, + loanSchedule); } @Bean diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java index d007d54c8..58ea36939 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImplTest.java @@ -18,10 +18,11 @@ */ package org.apache.fineract.portfolio.loanaccount.service; -import static org.mockito.Mockito.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import java.time.LocalDate; @@ -89,7 +90,7 @@ public class LoanAccrualsProcessingServiceImplTest { verify(loan, times(1)).isClosed(); verify(loanTransactionRepository, never()).saveAndFlush(any()); - verify(journalEntryWritePlatformService, never()).createJournalEntriesForLoan(any()); + verifyNoInteractions(journalEntryWritePlatformService); verify(businessEventNotifierService, never()).notifyPostBusinessEvent(any()); verify(loan, never()).addLoanTransaction(any()); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java index 121cc61a5..fc433bfef 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java @@ -19,7 +19,11 @@ package org.apache.fineract.portfolio.loanaccount.service; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -27,6 +31,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -38,6 +44,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; @@ -46,6 +53,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; @@ -71,6 +79,7 @@ class LoanChargeWritePlatformServiceImplTest { private static final LocalDate BUSINESS_DATE_AFTER = LocalDate.of(2024, 2, 26); private static final LocalDate BUSINESS_DATE_ON = MATURITY_DATE; private static final LocalDate BUSINESS_DATE_BEFORE = LocalDate.of(2024, 2, 14); + private static final String CURRENCY_CODE = "USD"; @InjectMocks private LoanChargeWritePlatformServiceImpl loanChargeWritePlatformService; @@ -138,19 +147,35 @@ class LoanChargeWritePlatformServiceImplTest { @Mock private LoanAccountService loanAccountService; + @Mock + private LoanAccountingBridgeMapper loanAccountingBridgeMapper; + @BeforeEach void setUp() { when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan); when(chargeRepository.findOneWithNotFoundDetection(anyLong())).thenReturn(chargeDefinition); when(chargeDefinition.getChargeTimeType()).thenReturn(SPECIFIED_DUE_DATE); + when(chargeDefinition.getCurrencyCode()).thenReturn(CURRENCY_CODE); when(loanChargeAssembler.createNewFromJson(loan, chargeDefinition, jsonCommand)).thenReturn(loanCharge); when(loan.repaymentScheduleDetail()).thenReturn(loanRepaymentScheduleDetail); - when(loan.hasCurrencyCodeOf(any())).thenReturn(true); + when(loan.hasCurrencyCodeOf(CURRENCY_CODE)).thenReturn(true); when(loanCharge.getChargePaymentMode()).thenReturn(ChargePaymentMode.REGULAR); when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); when(loanChargeRepository.saveAndFlush(any(LoanCharge.class))).thenReturn(loanCharge); when(loan.getCurrency()).thenReturn(monetaryCurrency); + when(monetaryCurrency.getCode()).thenReturn(CURRENCY_CODE); when(loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(any())).thenReturn(loan); + List existingTransactionIds = new ArrayList<>(); + List existingReversedTransactionIds = new ArrayList<>(); + when(loan.findExistingTransactionIds()).thenReturn(existingTransactionIds); + when(loan.findExistingReversedTransactionIds()).thenReturn(existingReversedTransactionIds); + + when(loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()).thenReturn(false); + when(loan.isCashBasedAccountingEnabledOnLoanProduct()).thenReturn(false); + when(loan.isUpfrontAccrualAccountingEnabledOnLoanProduct()).thenReturn(false); + + when(loanAccountingBridgeMapper.deriveAccountingBridgeData(anyString(), anyList(), anyList(), anyBoolean(), any(Loan.class))).thenReturn(new AccountingBridgeDataDTO()); + doNothing().when(journalEntryWritePlatformService).createJournalEntriesForLoan(any(AccountingBridgeDataDTO.class)); } @ParameterizedTest @@ -160,6 +185,10 @@ class LoanChargeWritePlatformServiceImplTest { when(loan.getMaturityDate()).thenReturn(maturityDate); when(loan.handleChargeAppliedTransaction(loanCharge, null)).thenReturn(loanTransaction); + if (isAccrualExpected) { + when(loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()).thenReturn(true); + } + try (MockedStatic mockedDateUtils = mockStatic(DateUtils.class)) { mockedDateUtils.when(DateUtils::getBusinessLocalDate).thenReturn(businessDate);