blob: 22a606c0a4f6bce7d581bc4e8276f5bb76d66f8b [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.avro.generic.GenericContainer;
import org.apache.fineract.avro.generator.ByteBufferSerializable;
import org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1;
import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1;
import org.apache.fineract.avro.loan.v1.LoanAmountDataV1;
import org.apache.fineract.avro.loan.v1.LoanChargeDataRangeViewV1;
import org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.generic.CurrencyDataMapper;
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanChargeDataMapper;
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanDelinquencyRangeDataMapper;
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroDateTimeMapper;
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class LoanDelinquencyRangeChangeBusinessEventSerializer implements BusinessEventSerializer {
private final LoanReadPlatformService service;
private final LoanDelinquencyRangeDataMapper mapper;
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
private final LoanChargeDataMapper chargeMapper;
private final CurrencyDataMapper currencyMapper;
private final AvroDateTimeMapper dataTimeMapper;
@Override
public <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
LoanDelinquencyRangeChangeBusinessEvent event = (LoanDelinquencyRangeChangeBusinessEvent) rawEvent;
LoanAccountData data = service.retrieveOne(event.get().getId());
Long id = data.getId();
String accountNumber = data.getAccountNo();
String externalId = data.getExternalId().getValue();
MonetaryCurrency loanCurrency = event.get().getCurrency();
CollectionData delinquentData = delinquencyReadPlatformService.calculateLoanCollectionData(id);
String delinquentDate = dataTimeMapper.mapLocalDate(delinquentData.getDelinquentDate());
List<LoanChargeDataRangeViewV1> charges = loanChargeReadPlatformService.retrieveLoanCharges(id)//
.stream()//
.map(chargeMapper::mapRangeView)//
.toList();
LoanAmountDataV1 amount = LoanAmountDataV1.newBuilder()//
.setPrincipalAmount(calculateDataSummary(event.get(),
(loan, installment) -> installment.getPrincipalOutstanding(loanCurrency).getAmount()))//
.setFeeAmount(calculateDataSummary(event.get(),
(loan, installment) -> installment.getFeeChargesOutstanding(loanCurrency).getAmount()))//
.setInterestAmount(calculateDataSummary(event.get(),
(loan, installment) -> installment.getInterestOutstanding(loanCurrency).getAmount()))//
.setPenaltyAmount(calculateDataSummary(event.get(),
(loan, installment) -> installment.getPenaltyChargesOutstanding(loanCurrency).getAmount()))//
.setTotalAmount(
calculateDataSummary(event.get(), (loan, installment) -> installment.getTotalOutstanding(loanCurrency).getAmount()))//
.build();
DelinquencyRangeDataV1 delinquencyRange = mapper.map(data.getDelinquencyRange());
List<LoanInstallmentDelinquencyBucketDataV1> installmentsDelinquencyData = calculateInstallmentLevelDelinquencyData(event.get(),
data);
LoanAccountDelinquencyRangeDataV1.Builder builder = LoanAccountDelinquencyRangeDataV1.newBuilder();
return builder//
.setLoanId(id)//
.setLoanAccountNo(accountNumber)//
.setLoanExternalId(externalId)//
.setDelinquencyRange(delinquencyRange)//
.setCharges(charges)//
.setAmount(amount)//
.setCurrency(currencyMapper.map(data.getCurrency()))//
.setDelinquentDate(delinquentDate)//
.setInstallmentDelinquencyBuckets(installmentsDelinquencyData).build();
}
private List<LoanInstallmentDelinquencyBucketDataV1> calculateInstallmentLevelDelinquencyData(Loan loan, LoanAccountData data) {
List<LoanInstallmentDelinquencyBucketDataV1> loanInstallmentDelinquencyData = new ArrayList<>();
if (loan.isEnableInstallmentLevelDelinquency()) {
Collection<LoanInstallmentDelinquencyTagData> installmentDelinquencyTags = delinquencyReadPlatformService
.retrieveLoanInstallmentsCurrentDelinquencyTag(loan.getId());
if (installmentDelinquencyTags != null && installmentDelinquencyTags.size() > 0) {
// group installments that are in same range
Map<Long, List<LoanInstallmentDelinquencyTagData>> installmentsInSameRange = installmentDelinquencyTags.stream().collect(
Collectors.groupingBy(installmentDelnquencyTags -> installmentDelnquencyTags.getDelinquencyRange().getId()));
// for installments in each range, get details from loan repayment schedule installment, add amounts,
// list charges
for (Map.Entry<Long, List<LoanInstallmentDelinquencyTagData>> installmentDelinquencyTagData : installmentsInSameRange
.entrySet()) {
// get installments details
List<LoanRepaymentScheduleInstallment> delinquentInstallmentsInSameRange = loan.getRepaymentScheduleInstallments()
.stream().filter(installment -> installmentDelinquencyTagData.getValue().stream()
.anyMatch(installmentTag -> installmentTag.getId().equals(installment.getId())))
.toList();
// add amounts
LoanAmountDataV1 amount = LoanAmountDataV1.newBuilder()//
.setPrincipalAmount(delinquentInstallmentsInSameRange.stream()
.map(instlment -> instlment.getPrincipalOutstanding(loan.getCurrency()).getAmount())
.reduce(BigDecimal.ZERO, BigDecimal::add))//
.setFeeAmount(delinquentInstallmentsInSameRange.stream()
.map(instlment -> instlment.getFeeChargesOutstanding(loan.getCurrency()).getAmount())
.reduce(BigDecimal.ZERO, BigDecimal::add))//
.setInterestAmount(delinquentInstallmentsInSameRange.stream()
.map(instlment -> instlment.getInterestOutstanding(loan.getCurrency()).getAmount())
.reduce(BigDecimal.ZERO, BigDecimal::add))//
.setPenaltyAmount(delinquentInstallmentsInSameRange.stream()
.map(instlment -> instlment.getPenaltyChargesOutstanding(loan.getCurrency()).getAmount())
.reduce(BigDecimal.ZERO, BigDecimal::add))//
.setTotalAmount(delinquentInstallmentsInSameRange.stream()
.map(instlment -> instlment.getTotalOutstanding(loan.getCurrency()).getAmount())
.reduce(BigDecimal.ZERO, BigDecimal::add))//
.build();
// get list of charges for installments in same range
List<LoanCharge> chargesForInstallmentsInSameRange = loan.getLoanCharges().stream().filter(loanCharge -> !loanCharge
.isPaid()
&& delinquentInstallmentsInSameRange.stream().anyMatch(installmentForCharge -> (DateUtils
.isAfter(loanCharge.getEffectiveDueDate(), installmentForCharge.getFromDate())
|| DateUtils.isEqual(loanCharge.getEffectiveDueDate(), installmentForCharge.getFromDate()))
&& (DateUtils.isBefore(loanCharge.getEffectiveDueDate(), installmentForCharge.getDueDate())
|| DateUtils.isEqual(loanCharge.getEffectiveDueDate(), installmentForCharge.getDueDate()))))
.toList();
List<LoanChargeDataRangeViewV1> charges = new ArrayList<>();
for (LoanCharge charge : chargesForInstallmentsInSameRange) {
LoanChargeDataRangeViewV1 chargeData = LoanChargeDataRangeViewV1.newBuilder().setId(charge.getId())
.setName(charge.name()).setAmount(charge.amountOutstanding())
.setCurrency(currencyMapper.map(data.getCurrency())).build();
charges.add(chargeData);
}
LoanInstallmentDelinquencyTagData.InstallmentDelinquencyRange delinquencyRange = installmentDelinquencyTagData
.getValue().get(0).getDelinquencyRange();
DelinquencyRangeDataV1 delinquencyRangeDataV1 = DelinquencyRangeDataV1.newBuilder().setId(delinquencyRange.getId())
.setClassification(delinquencyRange.getClassification()).setMinimumAgeDays(delinquencyRange.getMinimumAgeDays())
.setMaximumAgeDays(delinquencyRange.getMaximumAgeDays()).build();
LoanInstallmentDelinquencyBucketDataV1 installmentDelinquencyBucketDataV1 = LoanInstallmentDelinquencyBucketDataV1
.newBuilder().setDelinquencyRange(delinquencyRangeDataV1).setAmount(amount).setCharges(charges)
.setCurrency(currencyMapper.map(data.getCurrency())).build();
loanInstallmentDelinquencyData.add(installmentDelinquencyBucketDataV1);
}
}
}
return loanInstallmentDelinquencyData;
}
private BigDecimal calculateDataSummary(Loan loan, BiFunction<Loan, LoanRepaymentScheduleInstallment, BigDecimal> mapper) {
return loan.getRepaymentScheduleInstallments().stream().map(installment -> mapper.apply(loan, installment)).reduce(BigDecimal.ZERO,
BigDecimal::add);
}
@Override
public <T> boolean canSerialize(BusinessEvent<T> event) {
return event instanceof LoanDelinquencyRangeChangeBusinessEvent;
}
@Override
public Class<? extends GenericContainer> getSupportedSchema() {
return LoanAccountDelinquencyRangeDataV1.class;
}
}