blob: ebfbd5cd2142056c28b660e16b21c89cc40477b6 [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.portfolio.loanaccount.service;
import com.google.common.base.Splitter;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
@RequiredArgsConstructor
public class LoanChargeAssembler {
private final FromJsonHelper fromApiJsonHelper;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
private final LoanProductRepository loanProductRepository;
private final ExternalIdFactory externalIdFactory;
public Set<LoanCharge> fromParsedJson(final JsonElement element, List<LoanDisbursementDetails> disbursementDetails) {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
List<Long> disbursementChargeIds = new ArrayList<>();
if (jsonDisbursement != null && jsonDisbursement.size() > 0) {
for (int i = 0; i < jsonDisbursement.size(); i++) {
final JsonObject jsonObject = jsonDisbursement.get(i).getAsJsonObject();
if (jsonObject != null && jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName) != null) {
String chargeIds = jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString();
if (chargeIds != null) {
if (chargeIds.indexOf(",") != -1) {
Iterable<String> chargeId = Splitter.on(',').split(chargeIds);
for (String loanChargeId : chargeId) {
disbursementChargeIds.add(Long.parseLong(loanChargeId));
}
} else {
disbursementChargeIds.add(Long.parseLong(chargeIds));
}
}
}
}
}
final Set<LoanCharge> loanCharges = new HashSet<>();
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId));
final boolean isMultiDisbursal = loanProduct.isMultiDisburseLoan();
LocalDate expectedDisbursementDate = null;
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) {
final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
final Long id = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale);
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale);
final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType",
loanChargeElement, locale);
final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed("dueDate", loanChargeElement, dateFormat,
locale);
final Integer chargePaymentMode = this.fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", loanChargeElement,
locale);
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", loanChargeElement);
final ExternalId externalId = externalIdFactory.create(externalIdStr);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
if (chargeDefinition.isOverdueInstallment()) {
final String defaultUserMessage = "Installment charge cannot be added to the loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null,
chargeDefinition.getName());
}
ChargeTimeType chargeTime = null;
if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
}
ChargeCalculationType chargeCalculation = null;
if (chargeCalculationType != null) {
chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
}
ChargePaymentMode chargePaymentModeEnum = null;
if (chargePaymentMode != null) {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
}
if (!isMultiDisbursal) {
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments, externalId);
loanCharges.add(loanCharge);
} else {
if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) {
final JsonArray disbursementArray = topLevelJsonElement.get("disbursementData").getAsJsonArray();
if (disbursementArray.size() > 0) {
JsonObject disbursementDataElement = disbursementArray.get(0).getAsJsonObject();
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
LoanApiConstants.expectedDisbursementDateParameterName, disbursementDataElement, dateFormat,
locale);
}
}
if (ChargeTimeType.DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
if (chargeDefinition.isPercentageOfApprovedAmount()
&& disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) {
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments, externalId);
loanCharges.add(loanCharge);
if (loanCharge.isTrancheDisbursementCharge()) {
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
}
} else {
if (disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) {
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition,
disbursementDetail.principal(), amount, chargeTime, chargeCalculation,
disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
numberOfRepayments, externalId);
loanCharges.add(loanCharge);
if (loanCharge.isTrancheDisbursementCharge()) {
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
}
}
}
}
} else if (ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
if (ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition, disbursementDetail.principal(),
amount, chargeTime, chargeCalculation,
disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
numberOfRepayments, externalId);
loanCharges.add(loanCharge);
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
}
}
} else {
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments, externalId);
loanCharges.add(loanCharge);
}
}
} else {
final Long loanChargeId = id;
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId).orElse(null);
if (loanCharge != null) {
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
loanCharge.update(amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
}
}
}
}
}
}
return loanCharges;
}
public Set<Charge> getNewLoanTrancheCharges(final JsonElement element) {
final Set<Charge> associatedChargesForLoan = new HashSet<>();
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) {
final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
final Long id = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue())) {
associatedChargesForLoan.add(chargeDefinition);
}
}
}
}
}
return associatedChargesForLoan;
}
public LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && dueDate == null) {
final String defaultUserMessage = "Loan charge is missing due date.";
throw new LoanChargeWithoutMandatoryFieldException("loanCharge", "dueDate", defaultUserMessage, chargeDefinition.getId(),
chargeDefinition.getName());
}
return createNewFromJson(loan, chargeDefinition, command, dueDate);
}
public LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command,
final LocalDate dueDate) {
final Locale locale = command.extractLocale();
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount", locale);
final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null;
final ChargePaymentMode chargePaymentMode = null;
BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
switch (ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation())) {
case PERCENT_OF_AMOUNT:
if (command.hasParameter("principal")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal");
} else {
amountPercentageAppliedTo = loan.getPrincipal().getAmount();
}
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
if (command.hasParameter("principal") && command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal")
.add(command.bigDecimalValueOfParameterNamed("interest"));
} else {
amountPercentageAppliedTo = loan.getPrincipal().getAmount().add(loan.getTotalInterest());
}
break;
case PERCENT_OF_INTEREST:
if (command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("interest");
} else {
amountPercentageAppliedTo = loan.getTotalInterest();
}
break;
default:
break;
}
BigDecimal loanCharge = BigDecimal.ZERO;
if (ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType()).equals(ChargeTimeType.INSTALMENT_FEE)) {
BigDecimal percentage = amount;
if (percentage == null) {
percentage = chargeDefinition.getAmount();
}
loanCharge = loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()),
percentage);
}
// If charge type is specified due date and loan is multi disburment
// loan.
// Then we need to get as of this loan charge due date how much amount
// disbursed.
if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && loan.isMultiDisburmentLoan()) {
amountPercentageAppliedTo = BigDecimal.ZERO;
for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), dueDate)) {
amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.principal());
}
}
}
ExternalId externalId = externalIdFactory.createFromCommand(command, "externalId");
return new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
chargePaymentMode, null, loanCharge, externalId);
}
/*
* loanPrincipal is required for charges that are percentage based
*/
public LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final ExternalId externalId) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode,
numberOfRepayments, BigDecimal.ZERO, externalId);
}
}