FINERACT-1761: New Charge Adjustment transaction

This commit is contained in:
Adam Saghy
2022-11-10 09:36:10 +01:00
parent 48bbc9b266
commit cb1d1d0d5d
79 changed files with 3391 additions and 1613 deletions
@@ -205,6 +205,17 @@
"items": "org.apache.fineract.avro.loan.v1.LoanChargePaidByDataV1"
}
]
},
{
"default": null,
"name": "transactionRelations",
"type": [
"null",
{
"type": "array",
"items": "org.apache.fineract.avro.loan.v1.LoanTransactionRelationDataV1"
}
]
}
]
}
@@ -0,0 +1,47 @@
{
"name": "LoanTransactionRelationDataV1",
"namespace": "org.apache.fineract.avro.loan.v1",
"type": "record",
"fields": [
{
"name": "fromLoanTransaction",
"type": "int"
},
{
"default": null,
"name": "toLoanTransaction",
"type": [
"null",
"int"
]
},
{
"default": null,
"name": "toLoanCharge",
"type": [
"null",
"int"
]
},
{
"default": null,
"name": "amount",
"type": [
"null",
"bigdecimal"
]
},
{
"name": "relationType",
"type": "string"
},
{
"default": null,
"name": "paymentType",
"type": [
"null",
"string"
]
}
]
}
@@ -120,16 +120,16 @@ final class GLAccountsApiResourceSwagger {
public Boolean manualEntriesAllowed;
@Schema(example = "1")
public String type;
public Integer type;
@Schema(example = "10")
public String tagId;
public Long tagId;
@Schema(example = "1")
public Long parentId;
@Schema(example = "1")
public String usage;
public Integer usage;
@Schema(example = "Desc")
public String description;
@@ -144,7 +144,7 @@ final class GLAccountsApiResourceSwagger {
}
@Schema(example = "22")
public int resourceId;
public Long resourceId;
}
@Schema(description = "PutGLAccountsRequest")
@@ -175,7 +175,7 @@ final class GLAccountsApiResourceSwagger {
}
@Schema(example = "1")
public int resourceId;
public Long resourceId;
public PutGLAccountsResponsechangesSwagger changes;
}
@@ -193,7 +193,7 @@ final class GLAccountsApiResourceSwagger {
}
@Schema(example = "1")
public int resourceId;
public Long resourceId;
private DeleteGLAccountsRequestchangesSwagger changes;
}
@@ -24,6 +24,7 @@ import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
@RequiredArgsConstructor
@@ -58,4 +59,5 @@ public class LoanTransactionDTO {
private boolean isLoanToLoanTransfer;
private final String chargeRefundChargeType;
private final LoanChargeData loanChargeData;
}
@@ -66,6 +66,7 @@ import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatfor
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.client.domain.ClientTransaction;
import org.apache.fineract.portfolio.client.domain.ClientTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
@@ -125,6 +126,7 @@ public class AccountingProcessorHelper {
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");
final List<ChargePaymentDTO> feePaymentDetails = new ArrayList<>();
final List<ChargePaymentDTO> penaltyPaymentDetails = new ArrayList<>();
@@ -152,7 +154,7 @@ public class AccountingProcessorHelper {
}
final LoanTransactionDTO transaction = new LoanTransactionDTO(transactionOfficeId, paymentTypeId, transactionId,
transactionDate, transactionType, amount, principal, interest, fees, penalties, overPayments, reversed,
penaltyPaymentDetails, feePaymentDetails, isAccountTransfer, chargeRefundChargeType);
penaltyPaymentDetails, feePaymentDetails, isAccountTransfer, chargeRefundChargeType, loanChargeData);
Boolean isLoanToLoanTransfer = (Boolean) accountingBridgeData.get("isLoanToLoanTransfer");
transaction.setLoanToLoanTransfer(isLoanToLoanTransfer != null && isLoanToLoanTransfer);
newLoanTransactions.add(transaction);
@@ -622,6 +624,17 @@ public class AccountingProcessorHelper {
}
}
public void createDebitJournalEntryOrReversalForLoanCharges(final Office office, final String currencyCode,
final int accountMappingTypeId, final Long loanProductId, final Long chargeId, final Long loanId, final String transactionId,
final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
final GLAccount account = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId);
if (isReversal) {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
}
public void createCreditJournalEntryOrReversalForLoanCharges(final Office office, final String currencyCode,
final int accountMappingTypeId, final Long loanProductId, final Long loanId, final String transactionId,
final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
@@ -61,9 +61,10 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
/***
* Handle repayments, loan refunds, repayments at disbursement and reversal of Repayments and Repayments at
* disbursement
* disbursement (except charge adjustment)
***/
else if (loanTransactionDTO.getTransactionType().isRepaymentType()
else if ((loanTransactionDTO.getTransactionType().isRepaymentType()
&& !loanTransactionDTO.getTransactionType().isChargeAdjustment())
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
|| loanTransactionDTO.getTransactionType().isChargePayment()) {
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, false,
@@ -99,6 +100,109 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
else if (loanTransactionDTO.getTransactionType().isChargeback()) {
createJournalEntriesForChargeback(loanDTO, loanTransactionDTO, office);
}
// Logic for Charge Adjustment
else if (loanTransactionDTO.getTransactionType().isChargeAdjustment()) {
createJournalEntriesForChargeAdjustment(loanDTO, loanTransactionDTO, office);
}
}
}
private void createJournalEntriesForChargeAdjustment(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
// loan properties
final Long loanProductId = loanDTO.getLoanProductId();
final Long loanId = loanDTO.getLoanId();
final String currencyCode = loanDTO.getCurrencyCode();
// transaction properties
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
BigDecimal totalDebitAmount = new BigDecimal(0);
Map<GLAccount, BigDecimal> accountMap = new HashMap<>();
// handle principal payment (and reversals)
if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), paymentTypeId);
accountMap.put(account, principalAmount);
}
// handle interest payment (and reversals)
if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), paymentTypeId);
if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(interestAmount);
accountMap.put(account, amount);
} else {
accountMap.put(account, interestAmount);
}
}
// handle fees payment (and reversals)
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(feesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), paymentTypeId);
if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(feesAmount);
accountMap.put(account, amount);
} else {
accountMap.put(account, feesAmount);
}
}
// handle penalties payment (and reversals)
if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), paymentTypeId);
if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(penaltiesAmount);
accountMap.put(account, amount);
} else {
accountMap.put(account, penaltiesAmount);
}
}
// handle overpayment
if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
paymentTypeId);
if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(overPaymentAmount);
accountMap.put(account, amount);
} else {
accountMap.put(account, overPaymentAmount);
}
}
for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
entry.getValue(), isReversal, entry.getKey());
}
if (!(totalDebitAmount.compareTo(BigDecimal.ZERO) == 0)) {
Long chargeId = loanTransactionDTO.getLoanChargeData().getChargeId();
Integer accountMappingTypeId;
if (loanTransactionDTO.getLoanChargeData().isPenalty()) {
accountMappingTypeId = AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue();
} else {
accountMappingTypeId = AccrualAccountsForLoan.INCOME_FROM_FEES.getValue();
}
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId,
loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
}
}
@@ -21,11 +21,15 @@ package org.apache.fineract.accounting.journalentry.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.closure.domain.GLClosure;
import org.apache.fineract.accounting.common.AccountingConstants;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
import org.apache.fineract.accounting.journalentry.data.LoanDTO;
import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
@@ -58,9 +62,10 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
/***
* Logic for repayments, repayments at disbursement and reversal of Repayments and Repayments at
* disbursement
* disbursement (except charge adjustment)
***/
else if (loanTransactionDTO.getTransactionType().isRepaymentType()
else if ((loanTransactionDTO.getTransactionType().isRepaymentType()
&& !loanTransactionDTO.getTransactionType().isChargeAdjustment())
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
|| loanTransactionDTO.getTransactionType().isChargePayment()) {
createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office);
@@ -106,6 +111,109 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
else if (loanTransactionDTO.getTransactionType().isChargeback()) {
createJournalEntriesForChargeback(loanDTO, loanTransactionDTO, office);
}
// Logic for Charge Adjustment
else if (loanTransactionDTO.getTransactionType().isChargeAdjustment()) {
createJournalEntriesForChargeAdjustment(loanDTO, loanTransactionDTO, office);
}
}
}
private void createJournalEntriesForChargeAdjustment(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
// loan properties
final Long loanProductId = loanDTO.getLoanProductId();
final Long loanId = loanDTO.getLoanId();
final String currencyCode = loanDTO.getCurrencyCode();
// transaction properties
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
BigDecimal totalDebitAmount = new BigDecimal(0);
Map<GLAccount, BigDecimal> accountMap = new HashMap<>();
// handle principal payment (and reversals)
if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccountingConstants.CashAccountsForLoan.LOAN_PORTFOLIO.getValue(), paymentTypeId);
accountMap.put(account, principalAmount);
}
// handle interest payment (and reversals)
if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccountingConstants.CashAccountsForLoan.INTEREST_ON_LOANS.getValue(), paymentTypeId);
if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(interestAmount);
accountMap.put(account, amount);
} else {
accountMap.put(account, interestAmount);
}
}
// handle fees payment (and reversals)
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(feesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccountingConstants.CashAccountsForLoan.INCOME_FROM_FEES.getValue(), paymentTypeId);
if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(feesAmount);
accountMap.put(account, amount);
} else {
accountMap.put(account, feesAmount);
}
}
// handle penalties payment (and reversals)
if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), paymentTypeId);
if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(penaltiesAmount);
accountMap.put(account, amount);
} else {
accountMap.put(account, penaltiesAmount);
}
}
// handle overpayment
if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccountingConstants.CashAccountsForLoan.OVERPAYMENT.getValue(), paymentTypeId);
if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(overPaymentAmount);
accountMap.put(account, amount);
} else {
accountMap.put(account, overPaymentAmount);
}
}
for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
entry.getValue(), isReversal, entry.getKey());
}
if (!(totalDebitAmount.compareTo(BigDecimal.ZERO) == 0)) {
Long chargeId = loanTransactionDTO.getLoanChargeData().getChargeId();
Integer accountMappingTypeId;
if (loanTransactionDTO.getLoanChargeData().isPenalty()) {
accountMappingTypeId = AccountingConstants.CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue();
} else {
accountMappingTypeId = AccountingConstants.CashAccountsForLoan.INCOME_FROM_FEES.getValue();
}
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId,
loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
}
}
@@ -22,8 +22,8 @@ import java.util.Collection;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.stereotype.Component;
@Component
@@ -31,14 +31,14 @@ import org.springframework.stereotype.Component;
public class ApplyChargeToOverdueLoansBusinessStep implements LoanCOBBusinessStep {
private final LoanReadPlatformService loanReadPlatformService;
private final LoanWritePlatformService loanWritePlatformService;
private final LoanChargeWritePlatformService loanChargeWritePlatformService;
@Override
public Loan execute(Loan loan) {
final Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList = loanReadPlatformService
.retrieveAllOverdueInstallmentsForLoan(loan);
loanWritePlatformService.applyOverdueChargesForLoan(loan.getId(), overdueLoanScheduleDataList);
loanChargeWritePlatformService.applyOverdueChargesForLoan(loan.getId(), overdueLoanScheduleDataList);
return loan;
}
@@ -787,6 +787,15 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder adjustmentForLoanCharge(final Long loanId, final Long loanChargeId) {
this.actionName = "ADJUSTMENT";
this.entityName = "LOANCHARGE";
this.entityId = loanChargeId;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/charges/" + loanChargeId;
return this;
}
public CommandWrapperBuilder deleteLoanCharge(final Long loanId, final Long loanChargeId) {
this.actionName = "DELETE";
this.entityName = "LOANCHARGE";
@@ -0,0 +1,35 @@
/**
* 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.business.domain.loan.transaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
public class LoanChargeAdjustmentPostBusinessEvent extends LoanTransactionBusinessEvent {
private static final String TYPE = "LoanChargeAdjustmentPostBusinessEvent";
public LoanChargeAdjustmentPostBusinessEvent(LoanTransaction value) {
super(value);
}
@Override
public String getType() {
return TYPE;
}
}
@@ -0,0 +1,36 @@
/**
* 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.business.domain.loan.transaction;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public class LoanChargeAdjustmentPreBusinessEvent extends LoanBusinessEvent {
private static final String TYPE = "LoanChargeAdjustmentPreBusinessEvent";
public LoanChargeAdjustmentPreBusinessEvent(Loan value) {
super(value);
}
@Override
public String getType() {
return TYPE;
}
}
@@ -74,7 +74,10 @@ public class CurrencyData implements Serializable {
private String generateDisplayLabel() {
final StringBuilder builder = new StringBuilder(this.name).append(' ');
final StringBuilder builder = new StringBuilder(20);
if (this.name != null) {
builder.append(this.name).append(' ');
}
if (this.displaySymbol != null && !"".equalsIgnoreCase(this.displaySymbol.trim())) {
builder.append('(').append(this.displaySymbol).append(')');
@@ -52,7 +52,7 @@ final class ChargesApiResourceSwagger {
private GetChargesTimeTypeResponse() {}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "chargeTimeType.disbursement")
public String code;
@Schema(example = "Disbursement")
@@ -64,7 +64,7 @@ final class ChargesApiResourceSwagger {
private GetChargesAppliesToResponse() {}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "chargeAppliesTo.loan")
public String code;
@Schema(example = "Loan")
@@ -76,7 +76,7 @@ final class ChargesApiResourceSwagger {
private GetChargesCalculationTypeResponse() {}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "chargeCalculationType.flat")
public String code;
@Schema(example = "Flat")
@@ -88,7 +88,7 @@ final class ChargesApiResourceSwagger {
private GetChargesPaymentModeResponse() {}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "chargepaymentmode.accounttransfer")
public String code;
@Schema(example = "Account Transfer")
@@ -100,12 +100,12 @@ final class ChargesApiResourceSwagger {
@Schema(example = "Loan Service fee")
public String name;
@Schema(example = "true")
public String active;
public boolean active;
@Schema(example = "false")
public String penalty;
public boolean penalty;
public GetChargesCurrencyResponse currency;
@Schema(example = "230.56")
public Float amount;
public Double amount;
public GetChargesTimeTypeResponse chargeTimeType;
public GetChargesAppliesToResponse chargeAppliesTo;
public GetChargesCalculationTypeResponse chargeCalculationType;
@@ -126,7 +126,7 @@ final class ChargesApiResourceSwagger {
@Schema(example = "en")
public String locale;
@Schema(example = "230.56")
public Float amount;
public Double amount;
@Schema(example = "1")
public Integer chargeTimeType;
@Schema(example = "1")
@@ -134,11 +134,11 @@ final class ChargesApiResourceSwagger {
@Schema(example = "1")
public Integer chargePaymentMode;
@Schema(example = "true")
public String active;
public boolean active;
@Schema(example = "dd MMMM")
public String monthDayFormat;
@Schema(example = "false")
public String penalty;
public boolean penalty;
}
@Schema(description = "PostChargesResponse")
@@ -147,7 +147,7 @@ final class ChargesApiResourceSwagger {
private PostChargesResponse() {}
@Schema(example = "1")
public Integer resourceId;
public Long resourceId;
}
@Schema(description = "PutChargesChargeIdRequest")
@@ -165,7 +165,7 @@ final class ChargesApiResourceSwagger {
private PutChargesChargeIdResponse() {}
@Schema(example = "1")
public Integer resourceId;
public Long resourceId;
public PutChargesChargeIdRequest changes;
}
@@ -175,7 +175,7 @@ final class ChargesApiResourceSwagger {
private DeleteChargesChargeIdResponse() {}
@Schema(example = "1")
public Integer resourceId;
public Long resourceId;
}
@Schema(description = "GetChargesTemplateResponse")
@@ -188,7 +188,7 @@ final class ChargesApiResourceSwagger {
private GetChargesTemplateLoanChargeCalculationTypeOptions() {}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "chargeCalculationType.flat")
public String code;
@Schema(example = "Flat")
@@ -200,7 +200,7 @@ final class ChargesApiResourceSwagger {
private GetChargesTemplateLoanChargeTimeTypeOptions() {}
@Schema(example = "2")
public Integer id;
public Long id;
@Schema(example = "chargeTimeType.specifiedDueDate")
public String code;
@Schema(example = "Specified due date")
@@ -212,7 +212,7 @@ final class ChargesApiResourceSwagger {
private GetChargesTemplateFeeFrequencyOptions() {}
@Schema(example = "0")
public Integer id;
public Long id;
@Schema(example = "loanTermFrequency.periodFrequencyType.days")
public String code;
@Schema(example = "Days")
@@ -220,9 +220,9 @@ final class ChargesApiResourceSwagger {
}
@Schema(example = "false")
public String active;
public boolean active;
@Schema(example = "false")
public String penalty;
public boolean penalty;
public Set<GetChargesResponse.GetChargesCurrencyResponse> currencyOptions;
public Set<GetChargesResponse.GetChargesCalculationTypeResponse> chargeCalculationTypeOptions;
public Set<GetChargesResponse.GetChargesAppliesToResponse> chargeAppliesToOptions;
@@ -657,8 +657,11 @@ public class Charge extends AbstractPersistableCustom {
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(this.chargeTimeType);
final EnumOptionData chargeAppliesTo = ChargeEnumerations.chargeAppliesTo(this.chargeAppliesTo);
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(this.chargeCalculation);
final EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode);
final EnumOptionData feeFrequencyType = ChargeEnumerations.chargePaymentMode(this.feeFrequency);
final EnumOptionData chargePaymentMode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode);
EnumOptionData feeFrequencyType = null;
if (this.feeFrequency != null) {
feeFrequencyType = ChargeEnumerations.feeFrequencyType(this.feeFrequency);
}
GLAccountData accountData = null;
if (account != null) {
accountData = new GLAccountData().setId(account.getId()).setName(account.getName()).setGlCode(account.getGlCode());
@@ -675,7 +678,7 @@ public class Charge extends AbstractPersistableCustom {
final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null);
return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType,
chargePaymentmode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.enableFreeWithdrawal,
chargePaymentMode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.enableFreeWithdrawal,
this.freeWithdrawalFrequency, this.restartFrequency, this.restartFrequencyEnum, this.enablePaymentType, paymentTypeData,
this.minCap, this.maxCap, feeFrequencyType, accountData, taxGroupData);
}
@@ -23,6 +23,7 @@ import org.apache.fineract.portfolio.charge.domain.ChargeAppliesTo;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
public final class ChargeEnumerations {
@@ -191,4 +192,26 @@ public final class ChargeEnumerations {
return optionData;
}
public static EnumOptionData feeFrequencyType(final int id) {
return feeFrequencyType(PeriodFrequencyType.fromInt(id));
}
public static EnumOptionData feeFrequencyType(final PeriodFrequencyType frequencyType) {
EnumOptionData optionData;
switch (frequencyType) {
case DAYS -> optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(),
PeriodFrequencyType.DAYS.getCode(), "Daily");
case WEEKS -> optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(),
PeriodFrequencyType.WEEKS.getCode(), "Weekly");
case MONTHS -> optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(),
PeriodFrequencyType.MONTHS.getCode(), "Monthly");
case YEARS -> optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(),
PeriodFrequencyType.YEARS.getCode(), "Yearly");
case WHOLE_TERM -> optionData = new EnumOptionData(PeriodFrequencyType.WHOLE_TERM.getValue().longValue(),
PeriodFrequencyType.WHOLE_TERM.getCode(), "Whole term");
default -> throw new UnsupportedOperationException(frequencyType + " is not supported");
}
return optionData;
}
}
@@ -43,7 +43,6 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -52,6 +51,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
@@ -77,6 +77,9 @@ public class LoanChargesApiResource {
"amount", "amountPaid", "chargeOptions", "installmentChargeData"));
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
public static final String COMMAND_PAY = "pay";
public static final String COMMAND_WAIVE = "waive";
public static final String COMMAND_ADJUSTMENT = "adjustment";
private final PlatformSecurityContext context;
private final ChargeReadPlatformService chargeReadPlatformService;
@@ -98,10 +101,6 @@ public class LoanChargesApiResource {
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@@ -176,8 +175,8 @@ public class LoanChargesApiResource {
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
CommandProcessingResult result = null;
if (is(commandParam, "pay")) {
CommandProcessingResult result;
if (CommandParameterUtil.is(commandParam, COMMAND_PAY)) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().payLoanCharge(loanId, null).withJson(apiRequestBodyAsJson)
.build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
@@ -224,13 +223,16 @@ public class LoanChargesApiResource {
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
CommandProcessingResult result = null;
if (is(commandParam, "waive")) {
CommandProcessingResult result;
if (CommandParameterUtil.is(commandParam, COMMAND_WAIVE)) {
final CommandWrapper commandRequest = builder.waiveLoanCharge(loanId, loanChargeId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "pay")) {
} else if (CommandParameterUtil.is(commandParam, COMMAND_PAY)) {
final CommandWrapper commandRequest = builder.payLoanCharge(loanId, loanChargeId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (CommandParameterUtil.is(commandParam, COMMAND_ADJUSTMENT)) {
final CommandWrapper commandRequest = builder.adjustmentForLoanCharge(loanId, loanChargeId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
@@ -39,7 +39,7 @@ final class LoanChargesApiResourceSwagger {
private GetLoanChargeTimeType() {}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "chargeTimeType.disbursement")
public String code;
@Schema(example = "Disbursement")
@@ -51,7 +51,7 @@ final class LoanChargesApiResourceSwagger {
private GetLoanChargeCalculationType() {}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "chargeCalculationType.flat")
public String code;
@Schema(example = "Flat")
@@ -77,9 +77,9 @@ final class LoanChargesApiResourceSwagger {
}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "1")
public Integer chargeId;
public Long chargeId;
@Schema(example = "Loan Processing fee")
public String name;
public GetLoanChargeTimeType chargeTimeType;
@@ -121,7 +121,7 @@ final class LoanChargesApiResourceSwagger {
private GetLoanChargeTemplateChargeTimeType() {}
@Schema(example = "2")
public Integer id;
public Long id;
@Schema(example = "chargeTimeType.specifiedDueDate")
public String code;
@Schema(example = "Specified due date")
@@ -133,7 +133,7 @@ final class LoanChargesApiResourceSwagger {
private GetLoanChargeTemplateChargeAppliesTo() {}
@Schema(example = "1 ")
public Integer id;
public Long id;
@Schema(example = "chargeAppliesTo.loan")
public String code;
@Schema(example = "Loan")
@@ -141,7 +141,7 @@ final class LoanChargesApiResourceSwagger {
}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "Collection fee")
public String name;
@Schema(example = "true")
@@ -173,7 +173,7 @@ final class LoanChargesApiResourceSwagger {
private PostLoansLoanIdChargesRequest() {}
@Schema(example = "2")
public Integer chargeId;
public Long chargeId;
@Schema(example = "en")
public String locale;
@Schema(example = "100.00")
@@ -198,7 +198,7 @@ final class LoanChargesApiResourceSwagger {
@Schema(example = "1")
public Long loanId;
@Schema(example = "31")
public Integer resourceId;
public Long resourceId;
}
@Schema(description = " PutLoansLoanIdChargesChargeIdRequest")
@@ -228,7 +228,7 @@ final class LoanChargesApiResourceSwagger {
@Schema(example = "1")
public Long loanId;
@Schema(example = "6")
public Integer resourceId;
public Long resourceId;
public PutLoansLoanIdChargesChargeIdRequest changes;
}
@@ -243,6 +243,16 @@ final class LoanChargesApiResourceSwagger {
public String locale;
@Schema(example = "19 September 2013")
public String transactionDate;
@Schema(example = "1")
public Long chargeId;
@Schema(example = "19 September 2013")
public String dueDate;
@Schema(example = "1")
public Long installmentNumber;
@Schema(example = "100.00")
public Double amount;
@Schema(example = "786444UUUYYH7")
public String externalId;
}
@Schema(description = "PostLoansLoanIdChargesChargeIdResponse")
@@ -259,7 +269,26 @@ final class LoanChargesApiResourceSwagger {
@Schema(example = "1")
public Long savingsId;
@Schema(example = "12")
public Integer resourceId;
public Long resourceId;
public PostLoansLoanIdChargesChargeIdChanges changes;
static final class PostLoansLoanIdChargesChargeIdChanges {
@Schema(example = "en")
public String locale;
@Schema(example = "19 September 2013")
public LocalDate transactionDate;
@Schema(example = "1")
public Long chargeId;
@Schema(example = "19 September 2013")
public LocalDate dueDate;
@Schema(example = "1")
public Long installmentNumber;
@Schema(example = "100.00")
public Double amount;
@Schema(example = "786444UUUYYH7")
public String externalId;
}
}
@Schema(description = "DeleteLoansLoanIdChargesChargeIdResponse")
@@ -274,6 +303,6 @@ final class LoanChargesApiResourceSwagger {
@Schema(example = "1")
public Long loanId;
@Schema(example = "2")
public Integer resourceId;
public Long resourceId;
}
}
@@ -39,7 +39,7 @@ final class LoanTransactionsApiResourceSwagger {
private GetLoansTransactionType() {}
@Schema(example = "2")
public Integer id;
public Long id;
@Schema(example = "loanTransactionType.repayment")
public String code;
@Schema(example = "Repayment")
@@ -88,7 +88,7 @@ final class LoanTransactionsApiResourceSwagger {
private GetLoansType() {}
@Schema(example = "2")
public Integer id;
public Long id;
@Schema(example = "loanTransactionType.repayment")
public String code;
@Schema(example = "Repayment")
@@ -137,17 +137,19 @@ final class LoanTransactionsApiResourceSwagger {
public Long fromLoanTransaction;
@Schema(example = "10")
public Long toLoanTransaction;
@Schema(example = "10")
public Long toLoanCharge;
@Schema(example = "CHARGEBACK")
private String relationType;
public String relationType;
@Schema(example = "100.00")
private Double amount;
public Double amount;
@Schema(example = "Repayment Adjustment Chargeback")
private String paymentType;
public String paymentType;
}
@Schema(example = "3")
public Integer id;
public Long id;
public GetLoansType type;
@Schema(example = "[2012, 5, 14]")
public LocalDate date;
@@ -162,6 +164,8 @@ final class LoanTransactionsApiResourceSwagger {
public Double interestPortion;
@Schema(example = "20120514")
public String reversalExternalId;
@Schema(example = "20120514")
public String externalId;
@Schema(example = "[2012, 5, 18]")
public LocalDate reversedOnDate;
@Schema(example = "1000.00")
@@ -201,7 +205,7 @@ final class LoanTransactionsApiResourceSwagger {
@Schema(example = "3e7791ce-aa10-11ec-b909-0242ac120002")
public String externalId;
@Schema(example = "3")
public Integer paymentTypeId;
public Long paymentTypeId;
@Schema(example = "acc123")
public String accountNumber;
@Schema(example = "che123")
@@ -223,11 +227,11 @@ final class LoanTransactionsApiResourceSwagger {
private PostLoansLoanIdTransactionsResponse() {}
@Schema(example = "1")
public Integer officeId;
public Long officeId;
@Schema(example = "1")
public Integer clientId;
public Long clientId;
@Schema(example = "22")
public Integer resourceId;
public Long resourceId;
}
@Schema(description = "PostLoansLoanIdTransactionsTransactionIdRequest")
@@ -248,7 +252,7 @@ final class LoanTransactionsApiResourceSwagger {
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String reversalExternalId;
@Schema(example = "1")
public Integer paymentTypeId;
public Long paymentTypeId;
@Schema(example = "4ff9b1cb988b7")
public String externalId;
}
@@ -259,7 +263,7 @@ final class LoanTransactionsApiResourceSwagger {
private PostLoansLoanIdTransactionsTransactionIdResponse() {}
@Schema(example = "16")
public Integer resourceId;
public Long resourceId;
}
@Schema(description = "PutChargeTransactionChangesResponse")
@@ -276,9 +280,9 @@ final class LoanTransactionsApiResourceSwagger {
}
@Schema(example = "1")
public Integer resourceId;
public Long resourceId;
@Schema(example = "48")
public Integer loanId;
public Long loanId;
public PutChargeTransactionChangesResponse.Changes changes;
}
@@ -289,8 +293,8 @@ final class LoanTransactionsApiResourceSwagger {
private PutChargeTransactionChangesRequest() {}
@Schema(example = "1")
public Integer id;
public Long id;
@Schema(example = "2")
public Integer loanId;
public Long loanId;
}
}
@@ -267,7 +267,7 @@ final class LoansApiResourceSwagger {
public Double totalPaidLate;
@Schema(example = "0.00")
public Double totalOutstanding;
public Set<GetLoansLoanIdRepaymentPeriod> periods;
public List<GetLoansLoanIdRepaymentPeriod> periods;
}
static final class GetLoansLoanIdRepaymentPeriod {
@@ -635,6 +635,8 @@ final class LoansApiResourceSwagger {
public boolean refundForActiveLoans;
@Schema(example = "false")
public boolean creditBalanceRefund;
@Schema(example = "false")
public boolean chargeAdjustment;
}
static final class GetLoansLoanIdPaymentDetailData {
@@ -964,13 +966,13 @@ final class LoansApiResourceSwagger {
public GetLoansLoanIdSummary summary;
public GetLoansLoanIdRepaymentSchedule repaymentSchedule;
@Schema(description = "Set of GetLoansLoanIdTransactions")
public Set<GetLoansLoanIdTransactions> transactions;
public List<GetLoansLoanIdTransactions> transactions;
@Schema(description = "Set of GetLoansLoanIdDisbursementDetails")
public Set<GetLoansLoanIdDisbursementDetails> disbursementDetails;
@Schema(description = "Delinquent data")
public GetLoansLoanIdCollectionData delinquent;
@Schema(description = "Set of charges")
public Set<GetLoansLoanIdLoanChargeData> charges;
public List<GetLoansLoanIdLoanChargeData> charges;
public GetDelinquencyRangesResponse delinquencyRange;
@Schema(example = "false")
public Boolean fraud;
@@ -21,6 +21,8 @@ package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
@@ -32,6 +34,8 @@ import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
* Immutable data object for loan charge data.
*/
@Getter
@Builder
@AllArgsConstructor
public class LoanChargeData {
private final Long id;
@@ -20,9 +20,11 @@ package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class LoanInstallmentChargeData {
private final Integer installmentNumber;
@@ -32,47 +34,6 @@ public class LoanInstallmentChargeData {
private final BigDecimal amountWaived;
private final boolean paid;
private final boolean waived;
private BigDecimal amountAccrued;
private BigDecimal amountUnrecognized;
public LoanInstallmentChargeData(final Integer installmentNumber, final LocalDate dueDate, final BigDecimal amount,
final BigDecimal amountOutstanding, BigDecimal amountWaived, final boolean paid, final boolean waived) {
this.installmentNumber = installmentNumber;
this.dueDate = dueDate;
this.amount = amount;
this.amountOutstanding = amountOutstanding;
this.paid = paid;
this.waived = waived;
this.amountWaived = amountWaived;
}
public LoanInstallmentChargeData(final Integer installmentNumber, final LocalDate dueDate, final BigDecimal amount,
final BigDecimal amountOutstanding, final BigDecimal amountWaived, final boolean paid, final boolean waived,
final BigDecimal amountAccrued) {
this.installmentNumber = installmentNumber;
this.dueDate = dueDate;
this.amount = amount;
this.amountOutstanding = amountOutstanding;
this.paid = paid;
this.waived = waived;
this.amountWaived = amountWaived;
this.amountAccrued = amountAccrued;
}
public LoanInstallmentChargeData(final LoanInstallmentChargeData installmentChargeData, final BigDecimal amountUnrecognized) {
this.installmentNumber = installmentChargeData.installmentNumber;
this.dueDate = installmentChargeData.dueDate;
this.amount = installmentChargeData.amount;
this.amountOutstanding = installmentChargeData.amountOutstanding;
this.paid = installmentChargeData.paid;
this.waived = installmentChargeData.waived;
this.amountWaived = installmentChargeData.amountWaived;
this.amountAccrued = installmentChargeData.amountAccrued;
this.amountUnrecognized = amountUnrecognized;
}
public boolean isPaymentPending() {
return !(this.paid || this.waived);
}
}
@@ -51,7 +51,7 @@ public class LoanTransactionEnumData {
private final boolean refund;
private final boolean refundForActiveLoans;
private final boolean creditBalanceRefund;
private final boolean chargeAdjustment;
private final boolean chargeback;
public LoanTransactionEnumData(final Long id, final String code, final String value) {
@@ -80,6 +80,7 @@ public class LoanTransactionEnumData {
this.refundForActiveLoans = Long.valueOf(18).equals(this.id);
this.creditBalanceRefund = Long.valueOf(20).equals(this.id);
this.chargeback = Long.valueOf(25).equals(this.id);
this.chargeAdjustment = Long.valueOf(26).equals(this.id);
}
/**
@@ -96,7 +97,8 @@ public class LoanTransactionEnumData {
}
public boolean isRepaymentType() {
if (isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()) {
if (isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()
|| isChargeAdjustment()) {
return true;
}
return false;
@@ -32,6 +32,7 @@ public class LoanTransactionRelationData implements Serializable {
private Long fromLoanTransaction;
private Long toLoanTransaction;
private Long toLoanCharge;
private LoanTransactionRelationTypeEnum relationType;
private BigDecimal amount;
private String paymentType;
@@ -18,23 +18,20 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;
import java.util.Arrays;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.springframework.stereotype.Component;
// TODO: introduce tests for the state machine
@Component
@RequiredArgsConstructor
public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine {
private final List<LoanStatus> allowedLoanStatuses;
private static final List<LoanStatus> ALLOWED_LOAN_STATUSES = List.of(LoanStatus.values());
private final BusinessEventNotifierService businessEventNotifierService;
public DefaultLoanLifecycleStateMachine(LoanStatus[] allowedLoanStatuses, BusinessEventNotifierService businessEventNotifierService) {
this(Arrays.asList(allowedLoanStatuses), businessEventNotifierService);
}
@Override
public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) {
LoanStatus newStatus = getNextStatus(loanEvent, loan);
@@ -164,6 +161,11 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
newState = activeTransition();
}
break;
case LOAN_CHARGE_ADJUSTMENT:
if (from.hasStateOf(LoanStatus.CLOSED_OBLIGATIONS_MET)) {
newState = overpaidTransition();
}
break;
default:
break;
}
@@ -171,47 +173,47 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
}
private LoanStatus transferOnHold() {
return stateOf(LoanStatus.TRANSFER_ON_HOLD, this.allowedLoanStatuses);
return stateOf(LoanStatus.TRANSFER_ON_HOLD, ALLOWED_LOAN_STATUSES);
}
private LoanStatus transferInProgress() {
return stateOf(LoanStatus.TRANSFER_IN_PROGRESS, this.allowedLoanStatuses);
return stateOf(LoanStatus.TRANSFER_IN_PROGRESS, ALLOWED_LOAN_STATUSES);
}
private LoanStatus overpaidTransition() {
return stateOf(LoanStatus.OVERPAID, this.allowedLoanStatuses);
return stateOf(LoanStatus.OVERPAID, ALLOWED_LOAN_STATUSES);
}
private LoanStatus closedRescheduleOutstandingAmountTransition() {
return stateOf(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT, this.allowedLoanStatuses);
return stateOf(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT, ALLOWED_LOAN_STATUSES);
}
private LoanStatus closedWrittenOffTransition() {
return stateOf(LoanStatus.CLOSED_WRITTEN_OFF, this.allowedLoanStatuses);
return stateOf(LoanStatus.CLOSED_WRITTEN_OFF, ALLOWED_LOAN_STATUSES);
}
private LoanStatus closeObligationsMetTransition() {
return stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, this.allowedLoanStatuses);
return stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, ALLOWED_LOAN_STATUSES);
}
private LoanStatus activeTransition() {
return stateOf(LoanStatus.ACTIVE, this.allowedLoanStatuses);
return stateOf(LoanStatus.ACTIVE, ALLOWED_LOAN_STATUSES);
}
private LoanStatus withdrawnByClientTransition() {
return stateOf(LoanStatus.WITHDRAWN_BY_CLIENT, this.allowedLoanStatuses);
return stateOf(LoanStatus.WITHDRAWN_BY_CLIENT, ALLOWED_LOAN_STATUSES);
}
private LoanStatus approvedTransition() {
return stateOf(LoanStatus.APPROVED, this.allowedLoanStatuses);
return stateOf(LoanStatus.APPROVED, ALLOWED_LOAN_STATUSES);
}
private LoanStatus rejectedTransition() {
return stateOf(LoanStatus.REJECTED, this.allowedLoanStatuses);
return stateOf(LoanStatus.REJECTED, ALLOWED_LOAN_STATUSES);
}
private LoanStatus submittedTransition() {
return stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, this.allowedLoanStatuses);
return stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, ALLOWED_LOAN_STATUSES);
}
private LoanStatus stateOf(final LoanStatus state, final List<LoanStatus> allowedLoanStatuses) {
@@ -3379,7 +3379,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
private boolean doPostLoanTransactionChecks(final LocalDate transactionDate,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
boolean statusChanged = false;
if (isOverPaid()) {
boolean isOverpaid = getTotalOverpaid() != null && getTotalOverpaid().compareTo(BigDecimal.ZERO) > 0;
if (isOverpaid) {
// FIXME - kw - update account balance to negative amount.
handleLoanOverpayment(loanLifecycleStateMachine);
statusChanged = true;
@@ -3620,7 +3621,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (transactionForAdjustment.isNotRepaymentType() && transactionForAdjustment.isNotWaiver()
&& transactionForAdjustment.isNotCreditBalanceRefund()) {
final String errorMessage = "Only transactions of type repayment, waiver or credit balance refund can be adjusted.";
final String errorMessage = "Only (non-reversed) transactions of type repayment, waiver or credit balance refund can be adjusted.";
throw new InvalidLoanTransactionTypeException("transaction",
"adjustment.is.only.allowed.to.repayment.or.waiver.or.creditbalancerefund.transactions", errorMessage);
}
@@ -4935,8 +4936,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
switch (event) {
case LOAN_CREATED:
break;
case LOAN_APPROVED:
if (!isSubmittedAndPendingApproval()) {
final String defaultUserMessage = "Loan Account Approval is not allowed. Loan Account is not in submitted and pending approval state.";
@@ -5016,8 +5015,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
dataValidationErrors.add(error);
}
break;
case REPAID_IN_FULL:
break;
case LOAN_CHARGE_PAYMENT:
if (!isOpen()) {
final String defaultUserMessage = "Charge payment is not allowed. Loan Account is not Active.";
@@ -5082,6 +5079,14 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
dataValidationErrors.add(error);
}
break;
case LOAN_CHARGE_ADJUSTMENT:
if (!(getStatus().isActive() || getStatus().isClosedObligationsMet() || getStatus().isOverpaid())) {
final String defaultUserMessage = "Loan Charge Adjustment is not allowed. Loan Account must be either Active, Fully repaid or Overpaid.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.charge.adjustment.account.is.not.in.valid.state", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
default:
break;
}
@@ -30,7 +30,7 @@ public interface LoanAccountDomainService {
LoanTransaction makeRepayment(LoanTransactionType repaymentTransactionType, Loan loan, CommandProcessingResultBuilder builderResult,
LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId,
boolean isRecoveryRepayment, String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidatDetailDto,
boolean isRecoveryRepayment, String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto,
Boolean isHolidayValidationDone);
LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
@@ -76,6 +76,10 @@ public interface LoanAccountDomainService {
boolean isRecoveryRepayment, String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto,
Boolean isHolidayValidationDone, boolean isLoanToLoanTransfer);
void saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction);
void saveAndFlushLoanWithDataIntegrityViolationChecks(Loan loan);
void saveLoanWithDataIntegrityViolationChecks(Loan loan);
Map<String, Object> foreCloseLoan(Loan loan, LocalDate foreClourseDate, String noteText);
@@ -123,6 +123,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanCollateralManagementRepository loanCollateralManagementRepository;
private final DelinquencyWritePlatformService delinquencyWritePlatformService;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
@Transactional
@Override
@@ -193,7 +194,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
holidayDetailDto);
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
scheduleGeneratorDTO, isHolidayValidationDone);
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
@@ -310,38 +311,23 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return repaymentEvent;
}
private void saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) {
@Override
public void saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) {
try {
this.loanTransactionRepository.saveAndFlush(newRepaymentTransaction);
} catch (final JpaSystemException | DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").value(newRepaymentTransaction.getExternalId())
.failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors, e);
}
raiseValidationExceptionForUniqueConstraintViolation(e);
throw e;
}
}
private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
@Override
public void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
try {
this.loanRepositoryWrapper.saveAndFlush(loan);
} catch (final JpaSystemException | DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors, e);
}
raiseValidationExceptionForUniqueConstraintViolation(e);
throw e;
}
}
@@ -350,16 +336,22 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
try {
this.loanRepositoryWrapper.save(loan);
} catch (final JpaSystemException | DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors, e);
}
raiseValidationExceptionForUniqueConstraintViolation(e);
throw e;
}
}
private void raiseValidationExceptionForUniqueConstraintViolation(Exception e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique") || realCause.getMessage()
.contains("duplicate key value violates unique constraint \"m_loan_transaction_external_id_key\"")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors, e);
}
}
@@ -393,7 +385,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay);
loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds,
holidayDetailDTO, newPaymentTransaction, installmentNumber);
}
saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction);
@@ -427,10 +419,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService);
}
private void checkClientOrGroupActive(final Loan loan) {
final Client client = loan.client();
if (client != null) {
@@ -466,7 +454,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
loan.makeRefund(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
loan.makeRefund(newRefundTransaction, defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction);
@@ -659,7 +647,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final LoanTransaction newCreditBalanceRefundTransaction = LoanTransaction.creditBalanceRefund(loan, loan.getOffice(), refundAmount,
transactionDate, externalId);
loan.creditBalanceRefund(newCreditBalanceRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds,
loan.creditBalanceRefund(newCreditBalanceRefundTransaction, defaultLoanLifecycleStateMachine, existingTransactionIds,
existingReversedTransactionIds);
this.loanTransactionRepository.saveAndFlush(newCreditBalanceRefundTransaction);
@@ -699,7 +687,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
loan.makeRefundForActiveLoan(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds,
loan.makeRefundForActiveLoan(newRefundTransaction, defaultLoanLifecycleStateMachine, existingTransactionIds,
existingReversedTransactionIds, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
this.loanTransactionRepository.saveAndFlush(newRefundTransaction);
@@ -782,7 +770,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
List<Long> transactionIds = new ArrayList<>();
final ChangedTransactionDetail changedTransactionDetail = loan.handleForeClosureTransactions(payment,
defaultLoanLifecycleStateMachine(), scheduleGeneratorDTO);
defaultLoanLifecycleStateMachine, scheduleGeneratorDTO);
/***
* TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -41,6 +42,7 @@ import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -52,7 +54,9 @@ import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.command.LoanChargeCommand;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
@Entity
@Table(name = "m_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
@@ -1101,4 +1105,22 @@ public class LoanCharge extends AbstractPersistableCustom {
return ChargeTimeType.fromInt(this.chargeTime);
}
public LoanChargeData toData() {
EnumOptionData chargeTimeTypeData = new EnumOptionData((long) getChargeTimeType().ordinal(), getChargeTimeType().getCode(),
String.valueOf(getChargeTimeType().getValue()));
EnumOptionData chargeCalculationTypeData = new EnumOptionData((long) getChargeCalculation().ordinal(),
getChargeCalculation().getCode(), String.valueOf(getChargeCalculation().getValue()));
EnumOptionData chargePaymentModeData = new EnumOptionData((long) getChargePaymentMode().ordinal(), getChargePaymentMode().getCode(),
String.valueOf(getChargePaymentMode().getValue()));
Set<LoanInstallmentChargeData> loanInstallmentChargeDataSet = installmentCharges().stream().map(LoanInstallmentCharge::toData)
.collect(Collectors.toSet());
return LoanChargeData.builder().id(getId()).chargeId(getCharge().getId()).name(getCharge().getName())
.currency(getCharge().toData().getCurrency()).amount(amount).amountPaid(amountPaid).amountWaived(amountWaived)
.amountWrittenOff(amountWrittenOff).amountOutstanding(amountOutstanding).chargeTimeType(chargeTimeTypeData)
.submittedOnDate(submittedOnDate).dueDate(dueDate).chargeCalculationType(chargeCalculationTypeData).percentage(percentage)
.amountPercentageAppliedTo(amountPercentageAppliedTo).amountOrPercentage(amountOrPercentage).penalty(penaltyCharge)
.chargePaymentMode(chargePaymentModeData).paid(paid).waived(waived).loanId(loan.getId()).minCap(minCap).maxCap(maxCap)
.installmentChargeData(loanInstallmentChargeDataSet).externalId(externalId).build();
}
}
@@ -50,5 +50,6 @@ public enum LoanEvent {
LOAN_WITHDRAW_TRANSFER, //
LOAN_CREDIT_BALANCE_REFUND, //
LOAN_CHARGEBACK, //
LOAN_CHARGE_ADDED;
LOAN_CHARGE_ADDED, //
LOAN_CHARGE_ADJUSTMENT;
}
@@ -27,6 +27,7 @@ import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
@Entity
@Table(name = "m_loan_installment_charge")
@@ -322,4 +323,9 @@ public class LoanInstallmentCharge extends AbstractPersistableCustom implements
public void setInstallment(LoanRepaymentScheduleInstallment installment) {
this.installment = installment;
}
public LoanInstallmentChargeData toData() {
return LoanInstallmentChargeData.builder().installmentNumber(installment.getInstallmentNumber()).dueDate(installment.getDueDate())
.amount(amount).amountOutstanding(amountOutstanding).amountWaived(amountWaived).paid(paid).waived(waived).build();
}
}
@@ -172,6 +172,11 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
chargeRefundChargeType);
}
public static LoanTransaction chargeAdjustment(final Loan loan, final BigDecimal amount, final LocalDate transactionDate,
final String externalId) {
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, amount, transactionDate, externalId);
}
public void setLoanTransactionToRepaymentScheduleMappings(final Integer installmentId, final BigDecimal chargePerInstallment) {
for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : this.loanTransactionToRepaymentScheduleMappings) {
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
@@ -296,15 +301,12 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
public static boolean transactionAmountsMatch(final MonetaryCurrency currency, final LoanTransaction loanTransaction,
final LoanTransaction newLoanTransaction) {
if (loanTransaction.getAmount(currency).isEqualTo(newLoanTransaction.getAmount(currency))
return loanTransaction.getAmount(currency).isEqualTo(newLoanTransaction.getAmount(currency))
&& loanTransaction.getPrincipalPortion(currency).isEqualTo(newLoanTransaction.getPrincipalPortion(currency))
&& loanTransaction.getInterestPortion(currency).isEqualTo(newLoanTransaction.getInterestPortion(currency))
&& loanTransaction.getFeeChargesPortion(currency).isEqualTo(newLoanTransaction.getFeeChargesPortion(currency))
&& loanTransaction.getPenaltyChargesPortion(currency).isEqualTo(newLoanTransaction.getPenaltyChargesPortion(currency))
&& loanTransaction.getOverPaymentPortion(currency).isEqualTo(newLoanTransaction.getOverPaymentPortion(currency))) {
return true;
}
return false;
&& loanTransaction.getOverPaymentPortion(currency).isEqualTo(newLoanTransaction.getOverPaymentPortion(currency));
}
private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final LocalDate dateOf, final BigDecimal amount,
@@ -526,7 +528,8 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
}
public boolean isRepaymentType() {
return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund();
return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()
|| isChargeAdjustment();
}
public boolean isRepayment() {
@@ -549,6 +552,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return LoanTransactionType.CHARGE_REFUND.equals(getTypeOf()) && isNotReversed();
}
public boolean isChargeAdjustment() {
return LoanTransactionType.CHARGE_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isNotRepaymentType() {
return !isRepaymentType();
}
@@ -717,6 +724,13 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
thisTransactionData.put("loanChargesPaid", loanChargesPaidData);
}
LoanTransactionRelation loanTransactionRelation = loanTransactionRelations.stream()
.filter(e -> LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT.equals(e.getRelationType())).findAny().orElse(null);
if (loanTransactionRelation != null) {
LoanCharge loanCharge = loanTransactionRelation.getToCharge();
thisTransactionData.put("loanChargeData", loanCharge.toData());
}
return thisTransactionData;
}
@@ -874,10 +888,22 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return (loanTransactionRelations != null && loanTransactionRelations.size() > 0);
}
public boolean hasChargebackLoanTransactionRelations() {
return (loanTransactionRelations != null
&& loanTransactionRelations.stream().anyMatch(e -> LoanTransactionRelationTypeEnum.CHARGEBACK.equals(e.getRelationType())));
}
public Set<LoanTransactionRelation> getLoanTransactionRelations() {
return loanTransactionRelations;
}
public void copyLoanTransactionRelations(Set<LoanTransactionRelation> sourceLoanTransactionRelations) {
for (LoanTransactionRelation existingLoanTransactionRelation : sourceLoanTransactionRelations) {
loanTransactionRelations.add(new LoanTransactionRelation(this, existingLoanTransactionRelation.getToTransaction(),
existingLoanTransactionRelation.getToCharge(), existingLoanTransactionRelation.getRelationType()));
}
}
public BigDecimal getAmount() {
return amount;
}
@@ -23,6 +23,7 @@ import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
@@ -36,12 +37,18 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa
@Table(name = "m_loan_transaction_relation")
public class LoanTransactionRelation extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne
@JoinColumn(name = "from_loan_transaction_id", nullable = false)
private LoanTransaction fromTransaction;
@JoinColumn(name = "to_loan_transaction_id", nullable = false)
@ManyToOne
@JoinColumn(name = "to_loan_transaction_id")
private LoanTransaction toTransaction;
@ManyToOne
@JoinColumn(name = "to_loan_charge_id")
private LoanCharge toCharge;
@Enumerated(EnumType.ORDINAL)
@Column(name = "relation_type_enum", nullable = false)
private LoanTransactionRelationTypeEnum relationType;
@@ -51,15 +58,21 @@ public class LoanTransactionRelation extends AbstractAuditableWithUTCDateTimeCus
protected LoanTransactionRelation() {}
protected LoanTransactionRelation(@NotNull LoanTransaction fromTransaction, @NotNull LoanTransaction toTransaction,
protected LoanTransactionRelation(@NotNull LoanTransaction fromTransaction, LoanTransaction toTransaction, LoanCharge toCharge,
LoanTransactionRelationTypeEnum relationType) {
this.fromTransaction = fromTransaction;
this.toTransaction = toTransaction;
this.toCharge = toCharge;
this.relationType = relationType;
}
public static LoanTransactionRelation instance(@NotNull LoanTransaction fromTransaction, @NotNull LoanTransaction toTransaction,
public static LoanTransactionRelation linkToTransaction(@NotNull LoanTransaction fromTransaction,
@NotNull LoanTransaction toTransaction, LoanTransactionRelationTypeEnum relation) {
return new LoanTransactionRelation(fromTransaction, toTransaction, null, relation);
}
public static LoanTransactionRelation linkToCharge(@NotNull LoanTransaction fromTransaction, @NotNull LoanCharge loanCharge,
LoanTransactionRelationTypeEnum relation) {
return new LoanTransactionRelation(fromTransaction, toTransaction, relation);
return new LoanTransactionRelation(fromTransaction, null, loanCharge, relation);
}
}
@@ -25,7 +25,6 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface LoanTransactionRelationRepository
extends JpaRepository<LoanTransactionRelation, Long>, JpaSpecificationExecutor<LoanTransactionRelation> {
List<LoanTransactionRelation> findByFromTransactionAndRelationType(LoanTransaction loanTransaction,
LoanTransactionRelationTypeEnum relationType);
List<LoanTransactionRelation> findByFromTransaction(LoanTransaction fromTransaction);
}
@@ -21,7 +21,8 @@ package org.apache.fineract.portfolio.loanaccount.domain;
public enum LoanTransactionRelationTypeEnum {
INVALID(0, "loanTransactionType.invalid"), //
CHARGEBACK(1, "loanTransactionRelationType.chargeback");
CHARGEBACK(1, "loanTransactionRelationType.chargeback"), //
CHARGE_ADJUSTMENT(2, "loanTransactionRelationType.chargeAdjustment");
private final Integer value;
private final String code;
@@ -47,6 +48,7 @@ public enum LoanTransactionRelationTypeEnum {
return switch (transactionType) {
case 1 -> LoanTransactionRelationTypeEnum.CHARGEBACK;
case 2 -> LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT;
default -> LoanTransactionRelationTypeEnum.INVALID;
};
}
@@ -57,7 +57,8 @@ public enum LoanTransactionType {
PAYOUT_REFUND(22, "loanTransactionType.payoutRefund"), //
GOODWILL_CREDIT(23, "loanTransactionType.goodwillCredit"), //
CHARGE_REFUND(24, "loanTransactionType.chargeRefund"), //
CHARGEBACK(25, "loanTransactionType.chargeback");
CHARGEBACK(25, "loanTransactionType.chargeback"), //
CHARGE_ADJUSTMENT(26, "loanTransactionType.chargeAdjustment");
private final Integer value;
private final String code;
@@ -67,14 +68,6 @@ public enum LoanTransactionType {
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static LoanTransactionType fromInt(final Integer transactionType) {
if (transactionType == null) {
@@ -106,36 +99,45 @@ public enum LoanTransactionType {
case 23 -> LoanTransactionType.GOODWILL_CREDIT;
case 24 -> LoanTransactionType.CHARGE_REFUND;
case 25 -> LoanTransactionType.CHARGEBACK;
case 26 -> LoanTransactionType.CHARGE_ADJUSTMENT;
default -> LoanTransactionType.INVALID;
};
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isDisbursement() {
return this.value.equals(LoanTransactionType.DISBURSEMENT.getValue());
return this.equals(LoanTransactionType.DISBURSEMENT);
}
public boolean isRepaymentAtDisbursement() {
return this.value.equals(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue());
return this.equals(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT);
}
public boolean isRepayment() {
return this.value.equals(LoanTransactionType.REPAYMENT.getValue());
return this.equals(LoanTransactionType.REPAYMENT);
}
public boolean isMerchantIssuedRefund() {
return this.value.equals(LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
return this.equals(LoanTransactionType.MERCHANT_ISSUED_REFUND);
}
public boolean isPayoutRefund() {
return this.value.equals(LoanTransactionType.PAYOUT_REFUND.getValue());
return this.equals(LoanTransactionType.PAYOUT_REFUND);
}
public boolean isGoodwillCredit() {
return this.value.equals(LoanTransactionType.GOODWILL_CREDIT.getValue());
return this.equals(LoanTransactionType.GOODWILL_CREDIT);
}
public boolean isChargeRefund() {
return this.value.equals(LoanTransactionType.CHARGE_REFUND.getValue());
return this.equals(LoanTransactionType.CHARGE_REFUND);
}
public boolean isRepaymentType() {
@@ -143,39 +145,43 @@ public enum LoanTransactionType {
}
public boolean isRecoveryRepayment() {
return this.value.equals(LoanTransactionType.RECOVERY_REPAYMENT.getValue());
return this.equals(LoanTransactionType.RECOVERY_REPAYMENT);
}
public boolean isWaiveInterest() {
return this.value.equals(LoanTransactionType.WAIVE_INTEREST.getValue());
return this.equals(LoanTransactionType.WAIVE_INTEREST);
}
public boolean isWaiveCharges() {
return this.value.equals(LoanTransactionType.WAIVE_CHARGES.getValue());
return this.equals(LoanTransactionType.WAIVE_CHARGES);
}
public boolean isAccrual() {
return this.value.equals(LoanTransactionType.ACCRUAL.getValue());
return this.equals(LoanTransactionType.ACCRUAL);
}
public boolean isWriteOff() {
return this.value.equals(LoanTransactionType.WRITEOFF.getValue());
return this.equals(LoanTransactionType.WRITEOFF);
}
public boolean isChargePayment() {
return this.value.equals(LoanTransactionType.CHARGE_PAYMENT.getValue());
return this.equals(LoanTransactionType.CHARGE_PAYMENT);
}
public boolean isRefundForActiveLoan() {
return this.value.equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue());
return this.equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN);
}
public boolean isIncomePosting() {
return this.value.equals(LoanTransactionType.INCOME_POSTING.getValue());
return this.equals(LoanTransactionType.INCOME_POSTING);
}
public boolean isChargeback() {
return this.value.equals(LoanTransactionType.CHARGEBACK.getValue());
return this.equals(LoanTransactionType.CHARGEBACK);
}
public boolean isChargeAdjustment() {
return this.equals(LoanTransactionType.CHARGE_ADJUSTMENT);
}
}
@@ -185,6 +185,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} else {
loanTransaction.reverse(loanTransaction.getExternalId());
loanTransaction.updateExternalId(null);
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
}
}
@@ -0,0 +1,29 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanChargeAdjustmentException extends AbstractPlatformDomainRuleException {
public LoanChargeAdjustmentException(final String errorCode, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg." + errorCode, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -24,7 +24,7 @@ import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
@@ -35,7 +35,7 @@ import org.springframework.transaction.annotation.Transactional;
@CommandType(entity = "LOANCHARGE", action = "CREATE")
public class AddLoanChargeCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
private final LoanChargeWritePlatformService writePlatformService;
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
@Transactional
@@ -23,7 +23,7 @@ import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -32,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
@CommandType(entity = "LOANCHARGE", action = "DELETE")
public class DeleteLoanChargeCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
private final LoanChargeWritePlatformService writePlatformService;
@Transactional
@Override
@@ -0,0 +1,42 @@
/**
* 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.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@CommandType(entity = "LOANCHARGE", action = "ADJUSTMENT")
public class LoanChargeAdjustmentCommandHandler implements NewCommandSourceHandler {
private final LoanChargeWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return writePlatformService.adjustmentForLoanCharge(command.getLoanId(), command.entityId(), command);
}
}
@@ -24,7 +24,7 @@ import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
@@ -35,7 +35,7 @@ import org.springframework.transaction.annotation.Transactional;
@CommandType(entity = "LOAN", action = "CHARGEREFUND")
public class LoanChargeRefundCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
private final LoanChargeWritePlatformService writePlatformService;
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
@Transactional
@@ -23,7 +23,7 @@ import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -32,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
@CommandType(entity = "LOANCHARGE", action = "PAY")
public class PayLoanChargeCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
private final LoanChargeWritePlatformService writePlatformService;
@Transactional
@Override
@@ -23,7 +23,7 @@ import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -32,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
@CommandType(entity = "WAIVECHARGE", action = "UNDO")
public class UndoLoanChargeWaiveCommandHandler implements NewCommandSourceHandler {
final LoanWritePlatformService loanWritePlatformService;
final LoanChargeWritePlatformService loanWritePlatformService;
@Transactional
@Override
@@ -23,7 +23,7 @@ import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -32,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
@CommandType(entity = "LOANCHARGE", action = "UPDATE")
public class UpdateLoanChargeCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
private final LoanChargeWritePlatformService writePlatformService;
@Transactional
@Override
@@ -23,7 +23,7 @@ import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -32,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
@CommandType(entity = "LOANCHARGE", action = "WAIVE")
public class WaiveLoanChargeCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
private final LoanChargeWritePlatformService writePlatformService;
@Transactional
@Override
@@ -20,8 +20,8 @@ package org.apache.fineract.portfolio.loanaccount.jobs.applychargetooverdueloani
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
@@ -44,7 +44,7 @@ public class ApplyChargeToOverdueLoanInstallmentConfig {
@Autowired
private LoanReadPlatformService loanReadPlatformService;
@Autowired
private LoanWritePlatformService loanWritePlatformService;
private LoanChargeWritePlatformService loanChargeWritePlatformService;
@Bean
protected Step applyChargeToOverdueLoanInstallmentStep() {
@@ -61,6 +61,6 @@ public class ApplyChargeToOverdueLoanInstallmentConfig {
@Bean
public ApplyChargeToOverdueLoanInstallmentTasklet applyChargeToOverdueLoanInstallmentTasklet() {
return new ApplyChargeToOverdueLoanInstallmentTasklet(configurationDomainService, loanReadPlatformService,
loanWritePlatformService);
loanChargeWritePlatformService);
}
}
@@ -31,8 +31,8 @@ import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainR
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
@@ -44,7 +44,7 @@ public class ApplyChargeToOverdueLoanInstallmentTasklet implements Tasklet {
private final ConfigurationDomainService configurationDomainService;
private final LoanReadPlatformService loanReadPlatformService;
private final LoanWritePlatformService loanWritePlatformService;
private final LoanChargeWritePlatformService loanChargeWritePlatformService;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
@@ -68,7 +68,7 @@ public class ApplyChargeToOverdueLoanInstallmentTasklet implements Tasklet {
List<Throwable> exceptions = new ArrayList<>();
for (final Long loanId : overdueScheduleData.keySet()) {
try {
loanWritePlatformService.applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId));
loanChargeWritePlatformService.applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId));
} catch (final PlatformApiDataValidationException e) {
final List<ApiParameterError> errors = e.getErrors();
@@ -30,6 +30,7 @@ public interface LoanTransactionRelationMapper {
@Mapping(target = "fromLoanTransaction", source = "source.fromTransaction.id")
@Mapping(target = "toLoanTransaction", source = "source.toTransaction.id")
@Mapping(target = "toLoanCharge", source = "source.toCharge.id")
@Mapping(target = "amount", source = "source.toTransaction.amount")
@Mapping(target = "paymentType", source = "source.toTransaction.paymentDetail.paymentType.name")
LoanTransactionRelationData map(LoanTransactionRelation source);
@@ -47,13 +47,10 @@ import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollatera
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepositoryWrapper;
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.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollateralQuantity;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
@@ -107,7 +104,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
private final FromJsonHelper fromApiJsonHelper;
private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper;
private final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper;
private final LoanChargeApiJsonValidator loanChargeApiJsonValidator;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
public void validateForCreate(final String json, final boolean isMeetingMandatoryForJLGLoans, final LoanProduct loanProduct) {
@@ -391,15 +388,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();
LoanRepaymentScheduleTransactionProcessor processor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategy);
if (processor == null) {
Object[] values = loanRepaymentScheduleTransactionProcessorFactory.getStrategies().stream()
.map(TransactionProcessingStrategyData::getCode).toList().toArray(new String[0]);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName).isOneOfTheseValues(values);
}
// Validating whether the processor is existing
loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(transactionProcessingStrategy);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.linkAccountIdParameterName, element)) {
final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.linkAccountIdParameterName, element);
@@ -640,16 +630,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();
LoanRepaymentScheduleTransactionProcessor processor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategy);
if (processor == null) {
Object[] values = loanRepaymentScheduleTransactionProcessorFactory.getStrategies().stream()
.map(TransactionProcessingStrategyData::getCode).toList().toArray(new String[0]);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.isOneOfTheseValues(values);
}
// Validating whether the processor is existing
loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(transactionProcessingStrategy);
}
BigDecimal principal = null;
@@ -1282,46 +1264,10 @@ public final class LoanApplicationCommandFromApiJsonHelper {
}
public void validateLoanCharges(final Set<LoanCharge> charges, final List<ApiParameterError> dataValidationErrors) {
if (charges == null) {
return;
}
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
for (LoanCharge loanCharge : charges) {
String errorcode = null;
switch (loanCharge.getChargeCalculation()) {
case PERCENT_OF_AMOUNT:
if (loanCharge.isInstalmentFee()) {
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
}
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
if (loanCharge.isInstalmentFee()) {
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
} else if (loanCharge.isSpecifiedDueDate()) {
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
}
break;
case PERCENT_OF_INTEREST:
if (loanCharge.isSpecifiedDueDate()) {
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
}
break;
default:
break;
}
if (errorcode != null) {
baseDataValidator.reset().parameter("charges").failWithCode(errorcode);
}
}
}
public void validateLoanForInterestRecalculation(final Loan loan) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
validateLoanCharges(loan.getActiveCharges(), dataValidationErrors);
loanChargeApiJsonValidator.validateLoanCharges(loan.getActiveCharges(), dataValidationErrors);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
@@ -0,0 +1,271 @@
/**
* 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.serialization;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.springframework.stereotype.Component;
@Component
@AllArgsConstructor
public final class LoanChargeApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper;
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
public void validateAddLoanCharge(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Set<String> disbursementParameters = new HashSet<>(
Arrays.asList("chargeId", "amount", "dueDate", "locale", "dateFormat", "externalId"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanCharge");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", element);
baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
if (this.fromApiJsonHelper.parameterExists("dueDate", element)) {
final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed("dueDate", element);
baseDataValidator.reset().parameter("dueDate").value(dueDate).notBlank();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateUpdateOfLoanCharge(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("amount", "dueDate", "locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanCharge");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
if (this.fromApiJsonHelper.parameterExists("dueDate", element)) {
this.fromApiJsonHelper.extractLocalDateNamed("dueDate", element);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateChargePaymentTransaction(final String json, final boolean isChargeIdIncluded) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
Set<String> transactionParameters = null;
if (isChargeIdIncluded) {
transactionParameters = new HashSet<>(
Arrays.asList("transactionDate", "locale", "dateFormat", "chargeId", "dueDate", "installmentNumber"));
} else {
transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "locale", "dateFormat", "dueDate", "installmentNumber"));
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.payment.transaction");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
if (isChargeIdIncluded) {
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", element);
baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
}
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("installmentNumber", element);
baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateLoanChargeRefundTransaction(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final String loanChargeIdParam = "loanChargeId";
final String installmentNumberParam = "installmentNumber";
final String dueDateParam = "dueDate";
Set<String> transactionParameters = new HashSet<>(
Arrays.asList(loanChargeIdParam, dueDateParam, "locale", "dateFormat", installmentNumberParam, //
// remainder below relate to payment part of refund and not validated here
"transactionAmount", "externalId", "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber",
"routingCode", "receiptNumber", "bankNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.refund.transaction");
JsonElement element = this.fromApiJsonHelper.parse(json);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed(loanChargeIdParam, element);
baseDataValidator.reset().parameter(loanChargeIdParam).value(chargeId).notNull().integerGreaterThanZero();
final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(installmentNumberParam, element);
baseDataValidator.reset().parameter(installmentNumberParam).value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).ignoreIfNull().positiveAmount();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateLoanAdjustmentRequest(final Long loanId, final Long loanChargeId, final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
Set<String> transactionParameters = new HashSet<>(List.of("amount", "externalId", "locale"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.adjustment.request");
JsonElement element = this.fromApiJsonHelper.parse(json);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
baseDataValidator.reset().parameter("amount").value(amount).ignoreIfNull().positiveAmount();
baseDataValidator.reset().parameter("loanId").value(loanId).notNull().positiveAmount();
baseDataValidator.reset().parameter("loanChargeId").value(loanChargeId).notNull().positiveAmount();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateInstallmentChargeTransaction(final String json) {
if (StringUtils.isBlank(json)) {
return;
}
Set<String> transactionParameters = new HashSet<>(Arrays.asList("dueDate", "locale", "dateFormat", "installmentNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.waive.transaction");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("installmentNumber", element);
baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateLoanCharges(final Set<LoanCharge> charges, final List<ApiParameterError> dataValidationErrors) {
if (charges == null) {
return;
}
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
for (LoanCharge loanCharge : charges) {
String errorcode = null;
switch (loanCharge.getChargeCalculation()) {
case PERCENT_OF_AMOUNT:
if (loanCharge.isInstalmentFee()) {
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
}
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
if (loanCharge.isInstalmentFee()) {
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
} else if (loanCharge.isSpecifiedDueDate()) {
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
}
break;
case PERCENT_OF_INTEREST:
if (loanCharge.isSpecifiedDueDate()) {
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
}
break;
default:
break;
}
if (errorcode != null) {
baseDataValidator.reset().parameter("charges").failWithCode(errorcode);
}
}
}
}
@@ -338,60 +338,6 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateAddLoanCharge(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Set<String> disbursementParameters = new HashSet<>(
Arrays.asList("chargeId", "amount", "dueDate", "locale", "dateFormat", "externalId"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanCharge");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", element);
baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
if (this.fromApiJsonHelper.parameterExists("dueDate", element)) {
final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed("dueDate", element);
baseDataValidator.reset().parameter("dueDate").value(dueDate).notBlank();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateUpdateOfLoanCharge(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("amount", "dueDate", "locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanCharge");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
if (this.fromApiJsonHelper.parameterExists("dueDate", element)) {
this.fromApiJsonHelper.extractLocalDateNamed("dueDate", element);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateUpdateOfLoanOfficer(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
@@ -450,74 +396,6 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateChargePaymentTransaction(final String json, final boolean isChargeIdIncluded) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
Set<String> transactionParameters = null;
if (isChargeIdIncluded) {
transactionParameters = new HashSet<>(
Arrays.asList("transactionDate", "locale", "dateFormat", "chargeId", "dueDate", "installmentNumber"));
} else {
transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "locale", "dateFormat", "dueDate", "installmentNumber"));
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.payment.transaction");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
if (isChargeIdIncluded) {
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", element);
baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
}
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("installmentNumber", element);
baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateLoanChargeRefundTransaction(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final String loanChargeIdParam = "loanChargeId";
final String installmentNumberParam = "installmentNumber";
final String dueDateParam = "dueDate";
Set<String> transactionParameters = new HashSet<>(
Arrays.asList(loanChargeIdParam, dueDateParam, "locale", "dateFormat", installmentNumberParam, //
// remainder below relate to payment part of refund and not validated here
"transactionAmount", "externalId", "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber",
"routingCode", "receiptNumber", "bankNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.refund.transaction");
JsonElement element = this.fromApiJsonHelper.parse(json);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed(loanChargeIdParam, element);
baseDataValidator.reset().parameter(loanChargeIdParam).value(chargeId).notNull().integerGreaterThanZero();
final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(installmentNumberParam, element);
baseDataValidator.reset().parameter(installmentNumberParam).value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).ignoreIfNull().positiveAmount();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateMarkAsFraudLoan(final String json) {
if (StringUtils.isBlank(json)) {
return;
@@ -539,27 +417,6 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateInstallmentChargeTransaction(final String json) {
if (StringUtils.isBlank(json)) {
return;
}
Set<String> transactionParameters = new HashSet<>(Arrays.asList("dueDate", "locale", "dateFormat", "installmentNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.waive.transaction");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("installmentNumber", element);
baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateUpdateDisbursementDateAndAmount(final String json, LoanDisbursementDetails loanDisbursementDetails) {
if (StringUtils.isBlank(json)) {
@@ -98,7 +98,6 @@ import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -192,10 +191,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final GLIMAccountInfoRepository glimRepository;
private final LoanRepository loanRepository;
private final GSIMReadPlatformService gsimReadPlatformService;
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService);
}
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
@Transactional
@Override
@@ -1355,7 +1351,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
}
final Map<String, Object> changes = loan.loanApplicationApproval(currentUser, command, disbursementDataArray,
defaultLoanLifecycleStateMachine());
defaultLoanLifecycleStateMachine);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(loanId, EntityTables.LOAN.getName(),
StatusEnum.APPROVE.getCode().longValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
@@ -1462,7 +1458,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final Loan loan = retrieveLoanBy(loanId);
checkClientOrGroupActive(loan);
final Map<String, Object> changes = loan.undoApproval(defaultLoanLifecycleStateMachine());
final Map<String, Object> changes = loan.undoApproval(defaultLoanLifecycleStateMachine);
if (!changes.isEmpty()) {
// If loan approved amount is not same as loan amount demanded, then
@@ -1544,7 +1540,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
entityDatatableChecksWritePlatformService.runTheCheckForProduct(loanId, EntityTables.LOAN.getName(),
StatusEnum.REJECTED.getCode().longValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
final Map<String, Object> changes = loan.loanApplicationRejection(currentUser, command, defaultLoanLifecycleStateMachine());
final Map<String, Object> changes = loan.loanApplicationRejection(currentUser, command, defaultLoanLifecycleStateMachine);
if (!changes.isEmpty()) {
this.loanRepositoryWrapper.saveAndFlush(loan);
@@ -1581,7 +1577,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
StatusEnum.WITHDRAWN.getCode().longValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
final Map<String, Object> changes = loan.loanApplicationWithdrawnByApplicant(currentUser, command,
defaultLoanLifecycleStateMachine());
defaultLoanLifecycleStateMachine);
// Release attached collaterals
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
@@ -1619,7 +1615,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private Loan retrieveLoanBy(final Long loanId) {
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
loan.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
loan.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
return loan;
}
@@ -34,7 +34,6 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
@@ -60,7 +59,6 @@ import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
@@ -68,7 +66,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
@@ -109,18 +106,18 @@ public class LoanAssembler {
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanUtilService loanUtilService;
private final RateAssembler rateAssembler;
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
public Loan assembleFrom(final Long accountId) {
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
loanAccount.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper,
loanAccount.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
return loanAccount;
}
public void setHelpers(final Loan loanAccount) {
loanAccount.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper,
loanAccount.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
}
@@ -296,7 +293,7 @@ public class LoanAssembler {
if (loanApplication == null) {
throw new IllegalStateException("No loan application exists for either a client or group (or both).");
}
loanApplication.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper,
loanApplication.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
if (loanProduct.isMultiDisburseLoan()) {
@@ -314,16 +311,12 @@ public class LoanAssembler {
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms,
isHolidayEnabled, holidays, workingDays, element, disbursementDetails);
loanApplication.loanApplicationSubmittal(loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(),
submittedOnDate, externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
loanApplication.loanApplicationSubmittal(loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine, submittedOnDate,
externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
return loanApplication;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService);
}
public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) {
CodeValue codeValue = null;
if (codeValueId != null) {
@@ -43,6 +43,6 @@ public interface LoanChargeReadPlatformService {
Collection<LoanChargeData> retrieveLoanChargesForAccural(Long loanId);
Collection<LoanChargePaidByData> retriveLoanChargesPaidBy(Long chargeId, LoanTransactionType transactionType,
Collection<LoanChargePaidByData> retrieveLoanChargesPaidBy(Long chargeId, LoanTransactionType transactionType,
Integer installmentNumber);
}
@@ -65,7 +65,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
+ "lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, "
+ "lc.charge_time_enum as chargeTime, " + "lc.is_penalty as penalty, "
+ "lc.due_for_collection_as_of_date as dueAsOfDate, " + "lc.charge_calculation_enum as chargeCalculation, "
+ "lc.charge_payment_mode_enum as chargePaymentMode, " + "lc.is_paid_derived as paid, " + "lc.waived as waied, "
+ "lc.charge_payment_mode_enum as chargePaymentMode, " + "lc.is_paid_derived as paid, " + "lc.waived as waived, "
+ "lc.min_cap as minCap, lc.max_cap as maxCap, " + "lc.charge_amount_or_percentage as amountOrPercentage, "
+ "lc.loan_id as loanId, c.currency_code as currencyCode, oc.name as currencyName, "
+ "date(coalesce(dd.disbursedon_date,dd.expected_disburse_date)) as disbursementDate, "
@@ -112,7 +112,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final int chargePaymentMode = rs.getInt("chargePaymentMode");
final EnumOptionData paymentMode = ChargeEnumerations.chargePaymentMode(chargePaymentMode);
final boolean paid = rs.getBoolean("paid");
final boolean waived = rs.getBoolean("waied");
final boolean waived = rs.getBoolean("waived");
final BigDecimal minCap = rs.getBigDecimal("minCap");
final BigDecimal maxCap = rs.getBigDecimal("maxCap");
final BigDecimal amountOrPercentage = rs.getBigDecimal("amountOrPercentage");
@@ -227,7 +227,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
public String schema() {
return " lsi.installment as installmentNumber, lsi.duedate as dueAsOfDate, "
+ "lic.amount_outstanding_derived as amountOutstanding," + "lic.amount as amount, " + "lic.is_paid_derived as paid, "
+ "lic.amount_waived_derived as amountWaived, " + "lic.waived as waied " + "from m_loan_installment_charge lic "
+ "lic.amount_waived_derived as amountWaived, " + "lic.waived as waived " + "from m_loan_installment_charge lic "
+ "join m_loan_repayment_schedule lsi on lsi.id = lic.loan_schedule_id ";
}
@@ -239,9 +239,10 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final BigDecimal amount = rs.getBigDecimal("amount");
final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
final boolean paid = rs.getBoolean("paid");
final boolean waived = rs.getBoolean("waied");
final boolean waived = rs.getBoolean("waived");
return new LoanInstallmentChargeData(installmentNumber, dueAsOfDate, amount, amountOutstanding, amountWaived, paid, waived);
return LoanInstallmentChargeData.builder().installmentNumber(installmentNumber).dueDate(dueAsOfDate).amount(amount)
.amountOutstanding(amountOutstanding).amountWaived(amountWaived).paid(paid).waived(waived).build();
}
}
@@ -419,7 +420,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
sb.append("lic.is_paid_derived as paid, ");
sb.append("lic.amount_waived_derived as amountWaived, ");
sb.append(" sum(cp.amount) as amountAccrued, ");
sb.append("lic.waived as waied ");
sb.append("lic.waived as waived ");
sb.append("from m_loan_installment_charge lic ");
sb.append("join m_loan_repayment_schedule lsi on lsi.id = lic.loan_schedule_id ");
sb.append("left join (");
@@ -443,11 +444,12 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final BigDecimal amount = rs.getBigDecimal("amount");
final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
final boolean paid = rs.getBoolean("paid");
final boolean waived = rs.getBoolean("waied");
final boolean waived = rs.getBoolean("waived");
final BigDecimal amountAccrued = rs.getBigDecimal("amountAccrued");
return new LoanInstallmentChargeData(installmentNumber, dueAsOfDate, amount, amountOutstanding, amountWaived, paid, waived,
amountAccrued);
return LoanInstallmentChargeData.builder().installmentNumber(installmentNumber).dueDate(dueAsOfDate).amount(amount)
.amountOutstanding(amountOutstanding).amountWaived(amountWaived).paid(paid).waived(waived).amountAccrued(amountAccrued)
.build();
}
}
@@ -483,12 +485,16 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final Integer installmentNumber = rs.getInt("installmentNumber");
final BigDecimal amountUnrecognized = rs.getBigDecimal("amountUnrecognized");
LoanInstallmentChargeData installmentChargeData = this.installmentChargeDatas.get(installmentNumber);
return new LoanInstallmentChargeData(installmentChargeData, amountUnrecognized);
return LoanInstallmentChargeData.builder().installmentNumber(installmentChargeData.getInstallmentNumber())
.dueDate(installmentChargeData.getDueDate()).amount(installmentChargeData.getAmount())
.amountOutstanding(installmentChargeData.getAmountOutstanding()).amountWaived(installmentChargeData.getAmountWaived())
.paid(installmentChargeData.isPaid()).waived(installmentChargeData.isWaived())
.amountAccrued(installmentChargeData.getAmountAccrued()).amountUnrecognized(amountUnrecognized).build();
}
}
@Override
public Collection<LoanChargePaidByData> retriveLoanChargesPaidBy(Long chargeId, final LoanTransactionType transactionType,
public Collection<LoanChargePaidByData> retrieveLoanChargesPaidBy(Long chargeId, final LoanTransactionType transactionType,
final Integer installmentNumber) {
LoanChargesPaidByMapper rm = new LoanChargesPaidByMapper();
@@ -0,0 +1,45 @@
/**
* 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 java.util.Collection;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
public interface LoanChargeWritePlatformService {
CommandProcessingResult addLoanCharge(Long loanId, JsonCommand command);
CommandProcessingResult loanChargeRefund(Long loanId, JsonCommand command);
CommandProcessingResult undoWaiveLoanCharge(JsonCommand command);
CommandProcessingResult updateLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
CommandProcessingResult waiveLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
CommandProcessingResult deleteLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
CommandProcessingResult payLoanCharge(Long loanId, Long loanChargeId, JsonCommand command, boolean isChargeIdIncludedInJson);
CommandProcessingResult adjustmentForLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
void applyOverdueChargesForLoan(Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList);
}
@@ -110,7 +110,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
@@ -264,11 +263,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final String sql = "select " + rm.loanPaymentsSchema() + " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) "
+ " and (tr.is_reversed=false or tr.manually_adjusted_or_reversed = true) order by tr.transaction_date ASC,id ";
Collection<LoanTransactionData> loanTransactionData = this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR
// TODO: would worth to rework in the future. It is not nice to fetch relations one by one... might worth to
// give a try to get rid of native queries
for (LoanTransactionData loanTransaction : loanTransactionData) {
if (loanTransaction.getType().isRepaymentType()) {
loanTransaction
.setLoanTransactionRelations(this.retrieveLoanTransactionRelationsByLoanTransactionId(loanTransaction.getId()));
}
loanTransaction
.setLoanTransactionRelations(this.retrieveLoanTransactionRelationsByLoanTransactionId(loanTransaction.getId()));
}
return loanTransactionData;
} catch (final EmptyResultDataAccessException e) {
@@ -2399,7 +2398,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
public List<LoanTransactionRelationData> retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId) {
final LoanTransaction loanTransaction = this.loanTransactionRepository.getReferenceById(loanTransactionId);
List<LoanTransactionRelation> loanTransactionRelations = this.loanTransactionRelationRepository
.findByFromTransactionAndRelationType(loanTransaction, LoanTransactionRelationTypeEnum.CHARGEBACK);
.findByFromTransaction(loanTransaction);
return loanTransactionRelationMapper.map(loanTransactionRelations);
}
@@ -32,7 +32,7 @@ import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulk
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.springframework.transaction.annotation.Transactional;
public interface LoanWritePlatformService {
@@ -46,6 +46,10 @@ public interface LoanWritePlatformService {
CommandProcessingResult makeLoanRepayment(LoanTransactionType repaymentTransactionType, Long loanId, JsonCommand command,
boolean isRecoveryRepayment);
@Transactional
CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(LoanTransactionType repaymentTransactionType, Long loanId,
JsonCommand command, boolean isRecoveryRepayment, String chargeRefundChargeType);
Map<String, Object> makeLoanBulkRepayment(CollectionSheetBulkRepaymentCommand bulkRepaymentCommand);
CommandProcessingResult adjustLoanTransaction(Long loanId, Long transactionId, JsonCommand command);
@@ -60,18 +64,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult closeAsRescheduled(Long loanId, JsonCommand command);
CommandProcessingResult addLoanCharge(Long loanId, JsonCommand command);
CommandProcessingResult updateLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
CommandProcessingResult deleteLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
CommandProcessingResult waiveLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
CommandProcessingResult undoWaiveLoanCharge(JsonCommand command);
CommandProcessingResult loanChargeRefund(Long loanId, JsonCommand command);
CommandProcessingResult loanReassignment(Long loanId, JsonCommand command);
CommandProcessingResult bulkLoanReassignment(JsonCommand command);
@@ -89,8 +81,6 @@ public interface LoanWritePlatformService {
LoanTransaction acceptLoanTransfer(Loan loan, LocalDate transferDate, Office acceptedInOffice, Staff loanOfficer);
CommandProcessingResult payLoanCharge(Long loanId, Long loanChargeId, JsonCommand command, boolean isChargeIdIncludedInJson);
CommandProcessingResult undoWriteOff(Long loanId);
CommandProcessingResult updateDisbursementDateAndAmountForTranche(Long loanId, Long disbursementId, JsonCommand command);
@@ -103,8 +93,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command);
void applyOverdueChargesForLoan(Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDatas);
void recalculateInterest(long loanId);
CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command);
@@ -117,6 +105,8 @@ public interface LoanWritePlatformService {
CommandProcessingResult makeGLIMLoanRepayment(Long loanId, JsonCommand command);
void updateOriginalSchedule(Loan loan);
CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand command);
CommandProcessingResult markLoanAsFraud(Long loanId, JsonCommand command);
@@ -46,9 +46,7 @@ import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
@@ -315,17 +313,9 @@ public final class LoanProductDataValidator {
final String transactionProcessingStrategyCode = this.fromApiJsonHelper.extractStringNamed(TRANSACTION_PROCESSING_STRATEGY_CODE,
element);
baseDataValidator.reset().parameter(TRANSACTION_PROCESSING_STRATEGY_CODE).value(transactionProcessingStrategyCode).notNull();
LoanRepaymentScheduleTransactionProcessor processor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode);
if (processor == null) {
Object[] values = loanRepaymentScheduleTransactionProcessorFactory.getStrategies().stream()
.map(TransactionProcessingStrategyData::getCode).toList().toArray(new String[0]);
baseDataValidator.reset().parameter(TRANSACTION_PROCESSING_STRATEGY_CODE).isOneOfTheseValues(values);
}
baseDataValidator.reset().parameter(TRANSACTION_PROCESSING_STRATEGY_CODE).value(transactionProcessingStrategyCode).notBlank();
// Validating whether the processor is existing
loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME, element)) {
final Long delinquencyBucketId = this.fromApiJsonHelper.extractLongNamed(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
@@ -1093,8 +1083,9 @@ public final class LoanProductDataValidator {
if (this.fromApiJsonHelper.parameterExists(TRANSACTION_PROCESSING_STRATEGY_CODE, element)) {
final String transactionProcessingStrategyCode = this.fromApiJsonHelper.extractStringNamed(TRANSACTION_PROCESSING_STRATEGY_CODE,
element);
baseDataValidator.reset().parameter(TRANSACTION_PROCESSING_STRATEGY_CODE).value(transactionProcessingStrategyCode).notNull()
.integerGreaterThanZero();
baseDataValidator.reset().parameter(TRANSACTION_PROCESSING_STRATEGY_CODE).value(transactionProcessingStrategyCode).notBlank();
// Validating whether the processor is existing
loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
}
// grace validation
@@ -289,6 +289,8 @@ public final class LoanEnumerations {
LoanTransactionType.CHARGE_REFUND.getCode(), "Charge Refund");
case CHARGEBACK -> new LoanTransactionEnumData(LoanTransactionType.CHARGEBACK.getValue().longValue(),
LoanTransactionType.CHARGEBACK.getCode(), "Chargeback");
case CHARGE_ADJUSTMENT -> new LoanTransactionEnumData(LoanTransactionType.CHARGE_ADJUSTMENT.getValue().longValue(),
LoanTransactionType.CHARGE_ADJUSTMENT.getCode(), "Charge Adjustment");
};
}
@@ -87,4 +87,6 @@
<include file="parts/0065_add_bypass_loan_write_transaction_permission.xml" relativeToChangelogFile="true"/>
<include file="parts/0066_delinquency_classification_chargeback.xml" relativeToChangelogFile="true"/>
<include file="parts/0067_add_configurations_for_repayment_due_business_steps.xml" relativeToChangelogFile="true"/>
<include file="parts/0068_loan_charge_adjustment.xml" relativeToChangelogFile="true"/>
<include file="parts/0069_add_unique_constraint_for_reversal_external_id_of_loan_transactions.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<insert tableName="m_external_event_configuration">
<column name="type" value="LoanChargeAdjustmentPostBusinessEvent"/>
<column name="enabled" valueBoolean="false"/>
</insert>
<insert tableName="m_external_event_configuration">
<column name="type" value="LoanChargeAdjustmentPreBusinessEvent"/>
<column name="enabled" valueBoolean="false"/>
</insert>
</changeSet>
<changeSet id="2" author="fineract">
<addColumn tableName="m_loan_transaction_relation">
<column name="to_loan_charge_id" type="BIGINT">
<constraints nullable="true"/>
</column>
</addColumn>
<dropNotNullConstraint columnDataType="BIGINT"
columnName="to_loan_transaction_id"
tableName="m_loan_transaction_relation"/>
<addForeignKeyConstraint baseColumnNames="to_loan_charge_id" baseTableName="m_loan_transaction_relation"
constraintName="FK_m_loan_charge_to_relation" deferrable="false"
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="m_loan_charge" validate="true"/>
</changeSet>
<changeSet id="3" author="fineract">
<insert tableName="m_permission">
<column name="grouping" value="transaction_loan"/>
<column name="code" value="ADJUSTMENT_LOANCHARGE"/>
<column name="entity_name" value="LOANCHARGE"/>
<column name="action_name" value="ADJUSTMENT"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<addUniqueConstraint columnNames="reversal_external_id," constraintName="UNIQUE_REVERSAL_EXTERNAL_ID_LOAN_TRANSACTION"
tableName="m_loan_transaction"/>
</changeSet>
</databaseChangeLog>
@@ -88,7 +88,8 @@ public class ExternalEventConfigurationValidationServiceTest {
"RecurringDepositAccountCreateBusinessEvent", "SavingsActivateBusinessEvent", "SavingsApproveBusinessEvent",
"SavingsCloseBusinessEvent", "SavingsCreateBusinessEvent", "SavingsDepositBusinessEvent",
"SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent",
"ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent");
"ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
"LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent");
List<FineractPlatformTenant> tenants = Arrays
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
@@ -160,7 +161,8 @@ public class ExternalEventConfigurationValidationServiceTest {
"RecurringDepositAccountCreateBusinessEvent", "SavingsActivateBusinessEvent", "SavingsApproveBusinessEvent",
"SavingsCloseBusinessEvent", "SavingsCreateBusinessEvent", "SavingsDepositBusinessEvent",
"SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent",
"ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent");
"ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
"LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent");
List<FineractPlatformTenant> tenants = Arrays
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
@@ -41,7 +41,7 @@ class DefaultLoanLifecycleStateMachineTest {
@BeforeEach
public void setUp() {
underTest = new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService);
underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService);
}
@Test
@@ -20,6 +20,8 @@ package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
@@ -45,6 +47,28 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.client.models.BusinessDateRequest;
import org.apache.fineract.client.models.GetLoanTransactionRelation;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdSummary;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.PostChargesRequest;
import org.apache.fineract.client.models.PostChargesResponse;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostGLAccountsRequest;
import org.apache.fineract.client.models.PostGLAccountsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
@@ -64,6 +88,9 @@ import org.apache.fineract.integrationtests.common.savings.AccountTransferHelper
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -77,10 +104,9 @@ import org.slf4j.LoggerFactory;
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ClientLoanIntegrationTest {
private static final Logger LOG = LoggerFactory.getLogger(ClientLoanIntegrationTest.class);
public static final String MINIMUM_OPENING_BALANCE = "1000.0";
public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
private static final Logger LOG = LoggerFactory.getLogger(ClientLoanIntegrationTest.class);
private static final String NONE = "1";
private static final String CASH_BASED = "2";
private static final String ACCRUAL_PERIODIC = "3";
@@ -97,6 +123,9 @@ public class ClientLoanIntegrationTest {
private AccountTransferHelper accountTransferHelper;
private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
private BusinessDateHelper businessDateHelper;
private ChargesHelper chargesHelper;
private ClientHelper clientHelper;
@BeforeEach
public void setup() {
@@ -111,6 +140,9 @@ public class ClientLoanIntegrationTest {
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
this.periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(this.requestSpec, this.responseSpec);
this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
this.businessDateHelper = new BusinessDateHelper();
this.chargesHelper = new ChargesHelper();
this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
}
@Test
@@ -1112,39 +1144,37 @@ public class ClientLoanIntegrationTest {
private void validateCharge(Integer amountPercentage, final List<HashMap> loanCharges, final String amount, final String outstanding,
String amountPaid, String amountWaived) {
HashMap chargeDetail = getloanCharge(amountPercentage, loanCharges);
Assertions.assertTrue(Float.valueOf(amount).compareTo(Float.valueOf(String.valueOf(chargeDetail.get("amountOrPercentage")))) == 0);
Assertions.assertTrue(
Float.valueOf(outstanding).compareTo(Float.valueOf(String.valueOf(chargeDetail.get("amountOutstanding")))) == 0);
Assertions.assertTrue(Float.valueOf(amountPaid).compareTo(Float.valueOf(String.valueOf(chargeDetail.get("amountPaid")))) == 0);
Assertions.assertTrue(Float.valueOf(amountWaived).compareTo(Float.valueOf(String.valueOf(chargeDetail.get("amountWaived")))) == 0);
assertTrue(Float.valueOf(amount).compareTo(Float.valueOf(String.valueOf(chargeDetail.get("amountOrPercentage")))) == 0);
assertTrue(Float.valueOf(outstanding).compareTo(Float.valueOf(String.valueOf(chargeDetail.get("amountOutstanding")))) == 0);
assertTrue(Float.valueOf(amountPaid).compareTo(Float.valueOf(String.valueOf(chargeDetail.get("amountPaid")))) == 0);
assertTrue(Float.valueOf(amountWaived).compareTo(Float.valueOf(String.valueOf(chargeDetail.get("amountWaived")))) == 0);
}
private void validateChargeExcludePrecission(Integer amountPercentage, final List<HashMap> loanCharges, final String amount,
final String outstanding, String amountPaid, String amountWaived) {
DecimalFormat twoDForm = new DecimalFormat("#");
HashMap chargeDetail = getloanCharge(amountPercentage, loanCharges);
Assertions.assertTrue(Float.valueOf(twoDForm.format(Float.valueOf(amount)))
assertTrue(Float.valueOf(twoDForm.format(Float.valueOf(amount)))
.compareTo(Float.parseFloat(twoDForm.format(Float.valueOf(String.valueOf(chargeDetail.get("amountOrPercentage")))))) == 0);
Assertions.assertTrue(Float.valueOf(twoDForm.format(Float.valueOf(outstanding)))
assertTrue(Float.valueOf(twoDForm.format(Float.valueOf(outstanding)))
.compareTo(Float.valueOf(twoDForm.format(Float.parseFloat(String.valueOf(chargeDetail.get("amountOutstanding")))))) == 0);
Assertions.assertTrue(Float.valueOf(twoDForm.format(Float.parseFloat(amountPaid)))
assertTrue(Float.valueOf(twoDForm.format(Float.parseFloat(amountPaid)))
.compareTo(Float.valueOf(twoDForm.format(Float.parseFloat(String.valueOf(chargeDetail.get("amountPaid")))))) == 0);
Assertions.assertTrue(Float.valueOf(twoDForm.format(Float.parseFloat(amountWaived)))
assertTrue(Float.valueOf(twoDForm.format(Float.parseFloat(amountWaived)))
.compareTo(Float.valueOf(twoDForm.format(Float.parseFloat(String.valueOf(chargeDetail.get("amountWaived")))))) == 0);
}
private void validateNumberForEqual(String val, String val2) {
Assertions.assertTrue(Float.valueOf(val).compareTo(Float.valueOf(val2)) == 0);
assertTrue(Float.valueOf(val).compareTo(Float.valueOf(val2)) == 0);
}
private void validateNumberForEqualWithMsg(String msg, String val, String val2) {
Assertions.assertTrue(Float.valueOf(val).compareTo(Float.valueOf(val2)) == 0, msg + "expected " + val + " but was " + val2);
assertTrue(Float.valueOf(val).compareTo(Float.valueOf(val2)) == 0, msg + "expected " + val + " but was " + val2);
}
private void validateNumberForEqualExcludePrecission(String val, String val2) {
DecimalFormat twoDForm = new DecimalFormat("#");
Assertions.assertTrue(
Float.valueOf(twoDForm.format(Float.valueOf(val))).compareTo(Float.valueOf(twoDForm.format(Float.valueOf(val2)))) == 0,
assertTrue(Float.valueOf(twoDForm.format(Float.valueOf(val))).compareTo(Float.valueOf(twoDForm.format(Float.valueOf(val2)))) == 0,
String.format("%s is not equal to %s", val, val2));
}
@@ -5286,7 +5316,7 @@ public class ClientLoanIntegrationTest {
HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
ACCOUNT_TYPE_INDIVIDUAL);
Assertions.assertTrue(modifications.containsKey("submittedOnDate"));
assertTrue(modifications.containsKey("submittedOnDate"));
HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
@@ -5991,6 +6021,632 @@ public class ClientLoanIntegrationTest {
((Map) ((List) response.get("errors")).get(0)).get("defaultUserMessage"));
}
@Test
public void chargeAdjustmentChargeWrongParams() {
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.chargeAdjustment(0L, 0L, new PostLoansLoanIdChargesChargeIdRequest().amount(0.0)));
assertEquals(400, exception.getResponse().code());
assertTrue(exception.getMessage().contains("validation.msg.loan.charge.adjustment.request.amount.not.greater.than.zero"));
assertTrue(exception.getMessage().contains("validation.msg.loan.charge.adjustment.request.loanId.not.greater.than.zero"));
assertTrue(exception.getMessage().contains("validation.msg.loan.charge.adjustment.request.loanChargeId.not.greater.than.zero"));
exception = assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.chargeAdjustment(1L, 0L, new PostLoansLoanIdChargesChargeIdRequest().amount(0.0)));
assertEquals(400, exception.getResponse().code());
assertTrue(exception.getMessage().contains("validation.msg.loan.charge.adjustment.request.amount.not.greater.than.zero"));
assertTrue(exception.getMessage().contains("validation.msg.loan.charge.adjustment.request.loanChargeId.not.greater.than.zero"));
exception = assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.chargeAdjustment(1L, 1L, new PostLoansLoanIdChargesChargeIdRequest().amount(0.0)));
assertEquals(400, exception.getResponse().code());
assertTrue(exception.getMessage().contains("validation.msg.loan.charge.adjustment.request.amount.not.greater.than.zero"));
}
@Test
public void chargeAdjustmentChargeDoesNotExist() {
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
final Account expenseAccount = this.accountHelper.createExpenseAccount();
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount, expenseAccount,
overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011");
final Integer loanID = applyForLoanApplication(clientID, loanProductID);
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.chargeAdjustment((long) loanID, 1L, new PostLoansLoanIdChargesChargeIdRequest().amount(1.0)));
assertEquals(404, exception.getResponse().code());
assertTrue(exception.getMessage().contains("error.msg.loanCharge.id.invalid"));
}
@Test
public void chargeAdjustmentChargeDoesNotExistForLoan() {
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
final Account expenseAccount = this.accountHelper.createExpenseAccount();
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount, expenseAccount,
overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011");
final Integer loanID = applyForLoanApplication(clientID, loanProductID);
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanID, "1000");
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true));
LocalDate targetDate = LocalDate.of(2022, 9, 7);
final String penaltyCharge1AddedDate = dateFormatter.format(targetDate);
Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10"));
final Integer loanID2 = applyForLoanApplication(clientID, loanProductID);
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> loanTransactionHelper
.chargeAdjustment((long) loanID2, (long) penalty1LoanChargeId, new PostLoansLoanIdChargesChargeIdRequest().amount(1.0)));
assertEquals(404, exception.getResponse().code());
assertTrue(exception.getMessage().contains("error.msg.loanCharge.id.invalid.for.given.loan"));
}
@Test
public void chargeAdjustmentForUnpaidCharge() {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()).date("2022.11.01")
.dateFormat("yyyy.MM.dd").locale("en"));
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
final Account expenseAccount = this.accountHelper.createExpenseAccount();
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true));
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount, expenseAccount,
overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011");
final Integer loanID = applyForLoanApplication(clientID, loanProductID);
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanID, "1000");
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
assertEquals(2, loanSchedule.size());
assertEquals(0, loanSchedule.get(1).get("penaltyChargesDue"));
assertEquals(0, loanSchedule.get(1).get("penaltyChargesOutstanding"));
assertEquals(1000.0f, loanSchedule.get(1).get("totalDueForPeriod"));
assertEquals(1000.0f, loanSchedule.get(1).get("totalOutstandingForPeriod"));
LocalDate targetDate = LocalDate.of(2022, 9, 7);
final String penaltyCharge1AddedDate = dateFormatter.format(targetDate);
Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10"));
this.loanTransactionHelper.noAccrualTransactionForRepayment(loanID);
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
assertEquals(2, loanSchedule.size());
assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesOutstanding"));
assertEquals(1010.0f, loanSchedule.get(1).get("totalDueForPeriod"));
assertEquals(1010.0f, loanSchedule.get(1).get("totalOutstandingForPeriod"));
assertEquals(0, loanSchedule.get(1).get("totalWaivedForPeriod"));
HashMap loanSummary = this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "summary");
assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
assertEquals(10.0f, loanSummary.get("penaltyChargesOutstanding"));
assertEquals(0.0f, loanSummary.get("penaltyChargesWaived"));
assertEquals(1010.0f, loanSummary.get("totalOutstanding"));
assertEquals(0.0f, loanSummary.get("totalWaived"));
String externalId = UUID.randomUUID().toString();
PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResponse = this.loanTransactionHelper.chargeAdjustment((long) loanID,
(long) penalty1LoanChargeId, new PostLoansLoanIdChargesChargeIdRequest().amount(10.0).externalId(externalId));
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
assertEquals(2, loanSchedule.size());
assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesPaid"));
assertEquals(0.0f, loanSchedule.get(1).get("penaltyChargesOutstanding"));
assertEquals(1010.0f, loanSchedule.get(1).get("totalDueForPeriod"));
assertEquals(1000.0f, loanSchedule.get(1).get("totalOutstandingForPeriod"));
assertEquals(10.0f, loanSchedule.get(1).get("totalPaidForPeriod"));
loanSummary = this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "summary");
assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
assertEquals(0.0f, loanSummary.get("penaltyChargesOutstanding"));
assertEquals(10.0f, loanSummary.get("penaltyChargesPaid"));
assertEquals(1000.0f, loanSummary.get("totalOutstanding"));
GetLoansLoanIdTransactionsTransactionIdResponse chargeAdjustmentTransaction = this.loanTransactionHelper
.getLoanTransactionDetails((long) loanID, chargeAdjustmentResponse.getResourceId());
assertEquals(10.0, chargeAdjustmentTransaction.getAmount());
assertEquals(10.0, chargeAdjustmentTransaction.getPenaltyChargesPortion());
assertEquals("loanTransactionType.chargeAdjustment", chargeAdjustmentTransaction.getType().getCode());
assertEquals(externalId, chargeAdjustmentTransaction.getExternalId());
GetLoanTransactionRelation transactionRelation = chargeAdjustmentTransaction.getTransactionRelations().iterator().next();
assertEquals(chargeAdjustmentResponse.getResourceId(), transactionRelation.getFromLoanTransaction());
assertEquals((long) penalty1LoanChargeId, transactionRelation.getToLoanCharge());
assertEquals("CHARGE_ADJUSTMENT", transactionRelation.getRelationType());
PostLoansLoanIdTransactionsResponse repaymentResult = loanTransactionHelper.makeLoanRepayment((long) loanID,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en")
.transactionAmount(5.0));
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
assertEquals(2, loanSchedule.size());
assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesPaid"));
assertEquals(0.0f, loanSchedule.get(1).get("penaltyChargesOutstanding"));
assertEquals(1000.0f, loanSchedule.get(1).get("principalDue"));
assertEquals(5.0f, loanSchedule.get(1).get("principalPaid"));
assertEquals(995.0f, loanSchedule.get(1).get("principalOutstanding"));
assertEquals(1010.0f, loanSchedule.get(1).get("totalDueForPeriod"));
assertEquals(995.0f, loanSchedule.get(1).get("totalOutstandingForPeriod"));
assertEquals(15.0f, loanSchedule.get(1).get("totalPaidForPeriod"));
loanSummary = this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "summary");
assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
assertEquals(0.0f, loanSummary.get("penaltyChargesOutstanding"));
assertEquals(10.0f, loanSummary.get("penaltyChargesPaid"));
assertEquals(1000.0f, loanSummary.get("principalDisbursed"));
assertEquals(995.0f, loanSummary.get("principalOutstanding"));
assertEquals(5.0f, loanSummary.get("principalPaid"));
assertEquals(995.0f, loanSummary.get("totalOutstanding"));
Long chargeAdjustmentTxnId = repaymentResult.getResourceId() + 1;
chargeAdjustmentTransaction = this.loanTransactionHelper.getLoanTransactionDetails((long) loanID, chargeAdjustmentTxnId);
assertEquals(10.0, chargeAdjustmentTransaction.getAmount());
assertEquals(5.0, chargeAdjustmentTransaction.getPenaltyChargesPortion());
assertEquals(5.0, chargeAdjustmentTransaction.getPrincipalPortion());
assertEquals("loanTransactionType.chargeAdjustment", chargeAdjustmentTransaction.getType().getCode());
assertEquals(externalId, chargeAdjustmentTransaction.getExternalId());
transactionRelation = chargeAdjustmentTransaction.getTransactionRelations().iterator().next();
assertEquals(chargeAdjustmentTxnId, transactionRelation.getFromLoanTransaction());
assertEquals((long) penalty1LoanChargeId, transactionRelation.getToLoanCharge());
assertEquals("CHARGE_ADJUSTMENT", transactionRelation.getRelationType());
String uuid = UUID.randomUUID().toString();
this.loanTransactionHelper.reverseLoanTransaction((long) loanID, chargeAdjustmentTxnId,
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM yyyy").transactionDate("08 September 2022")
.transactionAmount(0.0).locale("en").reversalExternalId(uuid));
// Should fail due to external id collusion
assertThrows(CallFailedRuntimeException.class,
() -> this.loanTransactionHelper.reverseLoanTransaction((long) loanID, repaymentResult.getResourceId(),
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM yyyy")
.transactionDate("08 September 2022").transactionAmount(0.0).locale("en").reversalExternalId(uuid)));
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
assertEquals(2, loanSchedule.size());
assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
assertEquals(5.0f, loanSchedule.get(1).get("penaltyChargesPaid"));
assertEquals(5.0f, loanSchedule.get(1).get("penaltyChargesOutstanding"));
assertEquals(1000.0f, loanSchedule.get(1).get("principalDue"));
assertEquals(0, loanSchedule.get(1).get("principalPaid"));
assertEquals(1000.0f, loanSchedule.get(1).get("principalOutstanding"));
assertEquals(1010.0f, loanSchedule.get(1).get("totalDueForPeriod"));
assertEquals(1005.0f, loanSchedule.get(1).get("totalOutstandingForPeriod"));
assertEquals(5.0f, loanSchedule.get(1).get("totalPaidForPeriod"));
loanSummary = this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "summary");
assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
assertEquals(5.0f, loanSummary.get("penaltyChargesOutstanding"));
assertEquals(5.0f, loanSummary.get("penaltyChargesPaid"));
assertEquals(1000.0f, loanSummary.get("principalDisbursed"));
assertEquals(1000.0f, loanSummary.get("principalOutstanding"));
assertEquals(0.0f, loanSummary.get("principalPaid"));
assertEquals(1005.0f, loanSummary.get("totalOutstanding"));
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
}
@Test
public void chargeAdjustmentAccountingValidation() {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()).date("2022.11.01")
.dateFormat("yyyy.MM.dd").locale("en"));
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account assetFeeAndPenaltyAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
final Account expenseAccount = this.accountHelper.createExpenseAccount();
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
final PostGLAccountsResponse uniqueIncomeAccountForFee = this.accountHelper
.createGLAccount(new PostGLAccountsRequest().type(GLAccountType.INCOME.getValue())
.glCode(Utils.randomStringGenerator("UNIQUE_FEE_INCOME" + Calendar.getInstance().getTimeInMillis(), 5))
.manualEntriesAllowed(true)
.name(Utils.randomStringGenerator("UNIQUE_FEE_INCOME" + Calendar.getInstance().getTimeInMillis(), 5)).usage(1));
final PostGLAccountsResponse uniqueIncomeAccountForPenalty = this.accountHelper
.createGLAccount(new PostGLAccountsRequest().type(GLAccountType.INCOME.getValue())
.glCode(Utils.randomStringGenerator("UNIQUE_PENALTY_INCOME" + Calendar.getInstance().getTimeInMillis(), 5))
.manualEntriesAllowed(true)
.name(Utils.randomStringGenerator("UNIQUE_PENALTY_INCOME" + Calendar.getInstance().getTimeInMillis(), 5)).usage(1));
PostChargesResponse penaltyCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(true).amount(10.0)
.chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
.name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5)).chargeAppliesTo(1).locale("en")
.active(true));
PostChargesResponse feeCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(false).amount(9.0)
.chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
.name(Utils.randomStringGenerator("FEE_" + Calendar.getInstance().getTimeInMillis(), 5)).chargeAppliesTo(1).locale("en")
.active(true));
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
.withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat()
.withAccountingRulePeriodicAccrual(new Account[] { assetAccount, incomeAccount, expenseAccount, overpaymentAccount })
.withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0")
.withFeeToIncomeAccountMapping(feeCharge.getResourceId(), uniqueIncomeAccountForFee.getResourceId())
.withPenaltyToIncomeAccountMapping(penaltyCharge.getResourceId(), uniqueIncomeAccountForPenalty.getResourceId())
.withFeeAndPenaltyAssetAccount(assetFeeAndPenaltyAccount).build(null);
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
final Integer loanID = applyForLoanApplication(client.getClientId().intValue(), loanProductID);
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanID, "1000");
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
List<GetLoansLoanIdRepaymentPeriod> loanSchedulePeriods = loanDetails.getRepaymentSchedule().getPeriods();
assertEquals(2, loanSchedulePeriods.size());
assertEquals(0.0, loanSchedulePeriods.get(1).getPenaltyChargesDue());
assertEquals(0.0, loanSchedulePeriods.get(1).getPenaltyChargesOutstanding());
assertEquals(1000.0, loanSchedulePeriods.get(1).getTotalDueForPeriod());
assertEquals(1000.0, loanSchedulePeriods.get(1).getTotalOutstandingForPeriod());
LocalDate targetDate = LocalDate.of(2022, 9, 7);
final String penaltyCharge1AddedDate = dateFormatter.format(targetDate);
Integer penaltyLoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper
.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penaltyCharge.getResourceId()), penaltyCharge1AddedDate, "10"));
final String penalty1LoanChargeDate = dateFormatter.format(targetDate);
this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(penalty1LoanChargeDate);
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
List<GetLoansLoanIdTransactions> transactions = loanDetails.getTransactions();
assertEquals(10.0, transactions.get(1).getAmount());
assertTrue(transactions.get(1).getType().getAccrual());
assertEquals(10.0, transactions.get(1).getPenaltyChargesPortion());
Long accrualTransactionId = transactions.get(1).getId();
List<HashMap> journalEntries = this.journalEntryHelper.getJournalEntriesByTransactionId("L" + accrualTransactionId);
assertEquals(10.0f, (float) journalEntries.get(0).get("amount"));
assertEquals(uniqueIncomeAccountForPenalty.getResourceId().intValue(), (int) journalEntries.get(0).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(0).get("entryType")).get("value"));
assertEquals(10.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(assetFeeAndPenaltyAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
loanSchedulePeriods = loanDetails.getRepaymentSchedule().getPeriods();
assertEquals(2, loanSchedulePeriods.size());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesDue());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesOutstanding());
assertEquals(1010.0, loanSchedulePeriods.get(1).getTotalDueForPeriod());
assertEquals(1010.0, loanSchedulePeriods.get(1).getTotalOutstandingForPeriod());
GetLoansLoanIdSummary loanSummary = loanDetails.getSummary();
assertEquals(10.0, loanSummary.getPenaltyChargesCharged());
assertEquals(10.0, loanSummary.getPenaltyChargesOutstanding());
assertEquals(1010.0, loanSummary.getTotalOutstanding());
String externalId = UUID.randomUUID().toString();
PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResponse = this.loanTransactionHelper.chargeAdjustment((long) loanID,
(long) penaltyLoanChargeId, new PostLoansLoanIdChargesChargeIdRequest().amount(10.0).externalId(externalId));
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
loanSchedulePeriods = loanDetails.getRepaymentSchedule().getPeriods();
assertEquals(2, loanSchedulePeriods.size());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesDue());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanSchedulePeriods.get(1).getPenaltyChargesOutstanding());
assertEquals(1010.0, loanSchedulePeriods.get(1).getTotalDueForPeriod());
assertEquals(1000.0, loanSchedulePeriods.get(1).getTotalOutstandingForPeriod());
assertEquals(10.0, loanSchedulePeriods.get(1).getTotalPaidForPeriod());
loanSummary = loanDetails.getSummary();
assertEquals(10.0, loanSummary.getPenaltyChargesCharged());
assertEquals(0.0, loanSummary.getPenaltyChargesOutstanding());
assertEquals(10.0, loanSummary.getPenaltyChargesPaid());
assertEquals(1000.0, loanSummary.getTotalOutstanding());
transactions = loanDetails.getTransactions();
assertEquals(10.0, transactions.get(2).getAmount());
assertTrue(transactions.get(2).getType().getChargeAdjustment());
assertEquals(10.0, transactions.get(2).getPenaltyChargesPortion());
Long chargeAdjustmentTransactionId = transactions.get(2).getId();
journalEntries = this.journalEntryHelper.getJournalEntriesByTransactionId("L" + chargeAdjustmentTransactionId);
assertEquals(10.0f, (float) journalEntries.get(0).get("amount"));
assertEquals(uniqueIncomeAccountForPenalty.getResourceId().intValue(), (int) journalEntries.get(0).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(0).get("entryType")).get("value"));
assertEquals(10.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(assetFeeAndPenaltyAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
String uuid = UUID.randomUUID().toString();
this.loanTransactionHelper.reverseLoanTransaction((long) loanID, chargeAdjustmentTransactionId,
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM yyyy").transactionDate("08 September 2022")
.transactionAmount(0.0).locale("en").reversalExternalId(uuid));
journalEntries = this.journalEntryHelper.getJournalEntriesByTransactionId("L" + chargeAdjustmentTransactionId);
assertEquals(10.0f, (float) journalEntries.get(0).get("amount"));
assertEquals(uniqueIncomeAccountForPenalty.getResourceId().intValue(), (int) journalEntries.get(0).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(0).get("entryType")).get("value"));
assertEquals(10.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(assetFeeAndPenaltyAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
assertEquals(10.0f, (float) journalEntries.get(2).get("amount"));
assertEquals(uniqueIncomeAccountForPenalty.getResourceId().intValue(), (int) journalEntries.get(2).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(2).get("entryType")).get("value"));
assertEquals(10.0f, (float) journalEntries.get(3).get("amount"));
assertEquals(assetFeeAndPenaltyAccount.getAccountID(), (int) journalEntries.get(3).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(3).get("entryType")).get("value"));
targetDate = LocalDate.of(2022, 9, 10);
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
Integer feeLoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper
.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge.getResourceId()), feeCharge1AddedDate, "3"));
final String feeLoanChargeDate = dateFormatter.format(targetDate);
this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(feeLoanChargeDate);
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
transactions = loanDetails.getTransactions();
assertEquals(3.0, transactions.get(2).getAmount());
assertTrue(transactions.get(2).getType().getAccrual());
assertEquals(3.0, transactions.get(2).getFeeChargesPortion());
accrualTransactionId = transactions.get(2).getId();
journalEntries = this.journalEntryHelper.getJournalEntriesByTransactionId("L" + accrualTransactionId);
assertEquals(3.0f, (float) journalEntries.get(0).get("amount"));
assertEquals(uniqueIncomeAccountForFee.getResourceId().intValue(), (int) journalEntries.get(0).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(0).get("entryType")).get("value"));
assertEquals(3.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(assetFeeAndPenaltyAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
loanSchedulePeriods = loanDetails.getRepaymentSchedule().getPeriods();
assertEquals(2, loanSchedulePeriods.size());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesDue());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesOutstanding());
assertEquals(3.0, loanSchedulePeriods.get(1).getFeeChargesDue());
assertEquals(3.0, loanSchedulePeriods.get(1).getFeeChargesOutstanding());
assertEquals(1013.0, loanSchedulePeriods.get(1).getTotalDueForPeriod());
assertEquals(1013.0, loanSchedulePeriods.get(1).getTotalOutstandingForPeriod());
loanSummary = loanDetails.getSummary();
assertEquals(10.0, loanSummary.getPenaltyChargesCharged());
assertEquals(10.0, loanSummary.getPenaltyChargesOutstanding());
assertEquals(3.0, loanSummary.getFeeChargesCharged());
assertEquals(3.0, loanSummary.getFeeChargesOutstanding());
assertEquals(1013.0, loanSummary.getTotalOutstanding());
loanTransactionHelper.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("11 September 2022").locale("en").transactionAmount(5.0));
externalId = UUID.randomUUID().toString();
chargeAdjustmentResponse = this.loanTransactionHelper.chargeAdjustment((long) loanID, (long) feeLoanChargeId,
new PostLoansLoanIdChargesChargeIdRequest().amount(2.0).externalId(externalId));
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
loanSchedulePeriods = loanDetails.getRepaymentSchedule().getPeriods();
assertEquals(2, loanSchedulePeriods.size());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesDue());
assertEquals(7.0, loanSchedulePeriods.get(1).getPenaltyChargesPaid());
assertEquals(3.0, loanSchedulePeriods.get(1).getPenaltyChargesOutstanding());
assertEquals(3.0, loanSchedulePeriods.get(1).getFeeChargesDue());
assertEquals(0.0, loanSchedulePeriods.get(1).getFeeChargesPaid());
assertEquals(3.0, loanSchedulePeriods.get(1).getFeeChargesOutstanding());
assertEquals(1000.0, loanSchedulePeriods.get(1).getPrincipalDue());
assertEquals(0.0, loanSchedulePeriods.get(1).getPrincipalPaid());
assertEquals(1000.0, loanSchedulePeriods.get(1).getPrincipalOutstanding());
assertEquals(1013.0, loanSchedulePeriods.get(1).getTotalDueForPeriod());
assertEquals(1006.0, loanSchedulePeriods.get(1).getTotalOutstandingForPeriod());
assertEquals(7.0, loanSchedulePeriods.get(1).getTotalPaidForPeriod());
loanSummary = loanDetails.getSummary();
assertEquals(10.0, loanSummary.getPenaltyChargesCharged());
assertEquals(3.0, loanSummary.getPenaltyChargesOutstanding());
assertEquals(7.0, loanSummary.getPenaltyChargesPaid());
assertEquals(3.0, loanSummary.getFeeChargesCharged());
assertEquals(3.0, loanSummary.getFeeChargesOutstanding());
assertEquals(0.0, loanSummary.getFeeChargesPaid());
assertEquals(1000.0, loanSummary.getPrincipalOutstanding());
assertEquals(0.0, loanSummary.getPrincipalPaid());
assertEquals(7.0, loanSummary.getTotalRepayment());
assertEquals(1006.0, loanSummary.getTotalOutstanding());
transactions = loanDetails.getTransactions();
assertEquals(2.0, transactions.get(5).getAmount());
assertTrue(transactions.get(4).getType().getChargeAdjustment());
assertEquals(2.0, transactions.get(5).getPenaltyChargesPortion());
assertEquals(0.0, transactions.get(5).getFeeChargesPortion());
assertEquals(0.0, transactions.get(5).getPrincipalPortion());
chargeAdjustmentTransactionId = transactions.get(5).getId();
journalEntries = this.journalEntryHelper.getJournalEntriesByTransactionId("L" + chargeAdjustmentTransactionId);
assertEquals(2.0f, (float) journalEntries.get(0).get("amount"));
assertEquals(uniqueIncomeAccountForFee.getResourceId().intValue(), (int) journalEntries.get(0).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(0).get("entryType")).get("value"));
assertEquals(2.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(assetFeeAndPenaltyAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
externalId = UUID.randomUUID().toString();
chargeAdjustmentResponse = this.loanTransactionHelper.chargeAdjustment((long) loanID, (long) penaltyLoanChargeId,
new PostLoansLoanIdChargesChargeIdRequest().amount(7.0).externalId(externalId));
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
loanSchedulePeriods = loanDetails.getRepaymentSchedule().getPeriods();
assertEquals(2, loanSchedulePeriods.size());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesDue());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanSchedulePeriods.get(1).getPenaltyChargesOutstanding());
assertEquals(3.0, loanSchedulePeriods.get(1).getFeeChargesDue());
assertEquals(3.0, loanSchedulePeriods.get(1).getFeeChargesPaid());
assertEquals(0.0, loanSchedulePeriods.get(1).getFeeChargesOutstanding());
assertEquals(1000.0, loanSchedulePeriods.get(1).getPrincipalDue());
assertEquals(1.0, loanSchedulePeriods.get(1).getPrincipalPaid());
assertEquals(999.0, loanSchedulePeriods.get(1).getPrincipalOutstanding());
assertEquals(1013.0, loanSchedulePeriods.get(1).getTotalDueForPeriod());
assertEquals(999.0, loanSchedulePeriods.get(1).getTotalOutstandingForPeriod());
assertEquals(14.0, loanSchedulePeriods.get(1).getTotalPaidForPeriod());
loanSummary = loanDetails.getSummary();
assertEquals(10.0, loanSummary.getPenaltyChargesCharged());
assertEquals(0.0, loanSummary.getPenaltyChargesOutstanding());
assertEquals(10.0, loanSummary.getPenaltyChargesPaid());
assertEquals(3.0, loanSummary.getFeeChargesCharged());
assertEquals(0.0, loanSummary.getFeeChargesOutstanding());
assertEquals(3.0, loanSummary.getFeeChargesPaid());
assertEquals(999.0, loanSummary.getPrincipalOutstanding());
assertEquals(1.0, loanSummary.getPrincipalPaid());
assertEquals(14.0, loanSummary.getTotalRepayment());
assertEquals(999.0, loanSummary.getTotalOutstanding());
transactions = loanDetails.getTransactions();
assertEquals(7.0, transactions.get(6).getAmount());
assertTrue(transactions.get(6).getType().getChargeAdjustment());
assertEquals(3.0, transactions.get(6).getPenaltyChargesPortion());
assertEquals(3.0, transactions.get(6).getFeeChargesPortion());
assertEquals(1.0, transactions.get(6).getPrincipalPortion());
chargeAdjustmentTransactionId = transactions.get(6).getId();
journalEntries = this.journalEntryHelper.getJournalEntriesByTransactionId("L" + chargeAdjustmentTransactionId);
assertEquals(7.0f, (float) journalEntries.get(0).get("amount"));
assertEquals(uniqueIncomeAccountForPenalty.getResourceId().intValue(), (int) journalEntries.get(0).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(0).get("entryType")).get("value"));
if (assetAccount.getAccountID() == (int) journalEntries.get(1).get("glAccountId")) {
assertEquals(1.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(assetAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
assertEquals(6.0f, (float) journalEntries.get(2).get("amount"));
assertEquals(assetFeeAndPenaltyAccount.getAccountID(), (int) journalEntries.get(2).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(2).get("entryType")).get("value"));
} else {
assertEquals(1.0f, (float) journalEntries.get(2).get("amount"));
assertEquals(assetAccount.getAccountID(), (int) journalEntries.get(2).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(2).get("entryType")).get("value"));
assertEquals(6.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(assetFeeAndPenaltyAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
}
loanTransactionHelper.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("13 September 2022").locale("en").transactionAmount(998.0));
externalId = UUID.randomUUID().toString();
chargeAdjustmentResponse = this.loanTransactionHelper.chargeAdjustment((long) loanID, (long) feeLoanChargeId,
new PostLoansLoanIdChargesChargeIdRequest().amount(1.0).externalId(externalId));
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
loanSchedulePeriods = loanDetails.getRepaymentSchedule().getPeriods();
assertEquals(2, loanSchedulePeriods.size());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesDue());
assertEquals(10.0, loanSchedulePeriods.get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanSchedulePeriods.get(1).getPenaltyChargesOutstanding());
assertEquals(3.0, loanSchedulePeriods.get(1).getFeeChargesDue());
assertEquals(3.0, loanSchedulePeriods.get(1).getFeeChargesPaid());
assertEquals(0.0, loanSchedulePeriods.get(1).getFeeChargesOutstanding());
assertEquals(1000.0, loanSchedulePeriods.get(1).getPrincipalDue());
assertEquals(1000.0, loanSchedulePeriods.get(1).getPrincipalPaid());
assertEquals(0.0, loanSchedulePeriods.get(1).getPrincipalOutstanding());
assertEquals(1013.0, loanSchedulePeriods.get(1).getTotalDueForPeriod());
assertEquals(0.0, loanSchedulePeriods.get(1).getTotalOutstandingForPeriod());
assertEquals(1013.0, loanSchedulePeriods.get(1).getTotalPaidForPeriod());
loanSummary = loanDetails.getSummary();
assertEquals(10.0, loanSummary.getPenaltyChargesCharged());
assertEquals(0.0, loanSummary.getPenaltyChargesOutstanding());
assertEquals(10.0, loanSummary.getPenaltyChargesPaid());
assertEquals(3.0, loanSummary.getFeeChargesCharged());
assertEquals(0.0, loanSummary.getFeeChargesOutstanding());
assertEquals(3.0, loanSummary.getFeeChargesPaid());
assertEquals(0.0, loanSummary.getPrincipalOutstanding());
assertEquals(1000.0, loanSummary.getPrincipalPaid());
assertEquals(1013.0, loanSummary.getTotalRepayment());
assertEquals(0.0, loanSummary.getTotalOutstanding());
transactions = loanDetails.getTransactions();
assertEquals(1.0, transactions.get(8).getAmount());
assertTrue(transactions.get(8).getType().getChargeAdjustment());
assertEquals(0.0, transactions.get(8).getPenaltyChargesPortion());
assertEquals(0.0, transactions.get(8).getFeeChargesPortion());
assertEquals(1.0, transactions.get(8).getPrincipalPortion());
chargeAdjustmentTransactionId = transactions.get(8).getId();
journalEntries = this.journalEntryHelper.getJournalEntriesByTransactionId("L" + chargeAdjustmentTransactionId);
assertEquals(1.0f, (float) journalEntries.get(0).get("amount"));
assertEquals(uniqueIncomeAccountForFee.getResourceId().intValue(), (int) journalEntries.get(0).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(0).get("entryType")).get("value"));
assertEquals(1.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(assetAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
externalId = UUID.randomUUID().toString();
chargeAdjustmentResponse = this.loanTransactionHelper.chargeAdjustment((long) loanID, (long) penaltyLoanChargeId,
new PostLoansLoanIdChargesChargeIdRequest().amount(1.0).externalId(externalId));
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
transactions = loanDetails.getTransactions();
assertEquals(1.0, transactions.get(9).getAmount());
assertTrue(transactions.get(9).getType().getChargeAdjustment());
assertEquals(0.0, transactions.get(9).getPenaltyChargesPortion());
assertEquals(0.0, transactions.get(9).getFeeChargesPortion());
assertEquals(0.0, transactions.get(9).getPrincipalPortion());
assertEquals(1.0, transactions.get(9).getOverpaymentPortion());
chargeAdjustmentTransactionId = transactions.get(9).getId();
journalEntries = this.journalEntryHelper.getJournalEntriesByTransactionId("L" + chargeAdjustmentTransactionId);
assertEquals(1.0f, (float) journalEntries.get(0).get("amount"));
assertEquals(uniqueIncomeAccountForPenalty.getResourceId().intValue(), (int) journalEntries.get(0).get("glAccountId"));
assertEquals("DEBIT", ((HashMap) journalEntries.get(0).get("entryType")).get("value"));
assertEquals(1.0f, (float) journalEntries.get(1).get("amount"));
assertEquals(overpaymentAccount.getAccountID(), (int) journalEntries.get(1).get("glAccountId"));
assertEquals("CREDIT", ((HashMap) journalEntries.get(1).get("entryType")).get("value"));
assertTrue(loanDetails.getStatus().getOverpaid());
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
}
@Test
public void undoWaivedChargeWaiveTransactionDoesNotExist() {
final Account assetAccount = this.accountHelper.createAssetAccount();
@@ -118,14 +118,14 @@ public class DelinquencyAndChargebackIntegrationTest {
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate,
transactionAmount, loanId);
assertNotNull(loanIdTransactionsResponse);
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 0);
final Long transactionId = loanIdTransactionsResponse.getResourceId();
loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0);
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
validateLoanAccount(getLoansLoanIdResponse, "0.00", "1100.00", 0, doubleZERO);
final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, amountVal, 0,
responseSpec);
loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId, 1);
final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), amountVal,
0, responseSpec);
loanTransactionHelper.reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1);
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
@@ -597,7 +597,7 @@ public class DelinquencyBucketsIntegrationTest {
// Reverse the Previous Loan Repayment
PostLoansLoanIdTransactionsResponse loansLoanIdReverseTransactions = loanTransactionHelper.reverseLoanTransaction(loanId,
loansLoanIdTransactions.getResourceId(), operationDate, responseSpec);
loansLoanIdTransactions.getResourceId().intValue(), operationDate, responseSpec);
assertNotNull(loansLoanIdReverseTransactions);
log.info("Loan repayment reverse transaction id {}", loansLoanIdReverseTransactions.getResourceId());
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -90,7 +90,7 @@ public class LoanChargeSpecificDueDateTest {
String payloadJSON = ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, feeAmount, false);
final PostChargesResponse postChargesResponse = ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON);
assertNotNull(postChargesResponse);
final Integer loanChargeId = postChargesResponse.getResourceId();
final Long loanChargeId = postChargesResponse.getResourceId();
payloadJSON = LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(loanChargeId.toString(), operationDate, feeAmount);
PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse = loanTransactionHelper.addChargeForLoan(loanId, payloadJSON,
@@ -29,7 +29,7 @@ import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Set;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
@@ -87,19 +87,19 @@ public class LoanTransactionChargebackTest {
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
loanId);
assertNotNull(loanIdTransactionsResponse);
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
final Long transactionId = loanIdTransactionsResponse.getResourceId();
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met");
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00"));
final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "1000.00", 0,
responseSpec);
final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "1000.00",
0, responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("1000.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("1000.00"));
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
@@ -109,10 +109,11 @@ public class LoanTransactionChargebackTest {
// Try to reverse a Loan Transaction charge back
PostLoansLoanIdTransactionsResponse reverseTransactionResponse = loanTransactionHelper.reverseLoanTransaction(loanId,
chargebackTransactionId, operationDate, responseSpecError);
chargebackTransactionId.intValue(), operationDate, responseSpecError);
// Try to reverse a Loan Transaction repayment with linked transactions
reverseTransactionResponse = loanTransactionHelper.reverseLoanTransaction(loanId, transactionId, operationDate, responseSpecError);
reverseTransactionResponse = loanTransactionHelper.reverseLoanTransaction(loanId, transactionId.intValue(), operationDate,
responseSpecError);
}
@Test
@@ -135,13 +136,13 @@ public class LoanTransactionChargebackTest {
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
loanId);
assertNotNull(loanIdTransactionsResponse);
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("666.67"));
final Long transactionId = loanIdTransactionsResponse.getResourceId();
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("666.67"));
final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, amount.toString(),
0, responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("666.67"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("1000.00"));
final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(),
amount.toString(), 0, responseSpec);
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("666.67"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("1000.00"));
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
@@ -170,7 +171,7 @@ public class LoanTransactionChargebackTest {
GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
Set<GetLoansLoanIdTransactions> loanTransactions = getLoansLoanIdResponse.getTransactions();
List<GetLoansLoanIdTransactions> loanTransactions = getLoansLoanIdResponse.getTransactions();
assertNotNull(loanTransactions);
log.info("Loan Id {} with {} transactions", loanId, loanTransactions.size());
assertEquals(2, loanTransactions.size());
@@ -203,19 +204,19 @@ public class LoanTransactionChargebackTest {
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
loanId);
assertNotNull(loanIdTransactionsResponse);
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
final Long transactionId = loanIdTransactionsResponse.getResourceId();
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met");
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00"));
Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "500.00", 0,
Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "500.00", 0,
responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("500.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("500.00"));
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
@@ -237,10 +238,11 @@ public class LoanTransactionChargebackTest {
}
}
chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "300.00", 0, responseSpec);
chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "300.00", 0,
responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 2, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("800.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 2, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("800.00"));
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
@@ -281,19 +283,19 @@ public class LoanTransactionChargebackTest {
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
loanId);
assertNotNull(loanIdTransactionsResponse);
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
final Long transactionId = loanIdTransactionsResponse.getResourceId();
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.overpaid");
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00"));
final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "200.00", 0,
final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "200.00", 0,
responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("100.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("100.00"));
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
@@ -320,19 +322,19 @@ public class LoanTransactionChargebackTest {
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
loanId);
assertNotNull(loanIdTransactionsResponse);
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
final Long transactionId = loanIdTransactionsResponse.getResourceId();
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.overpaid");
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00"));
final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "100.00", 0,
final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "100.00", 0,
responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("0.00"));
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
@@ -355,21 +357,21 @@ public class LoanTransactionChargebackTest {
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
loanId);
assertNotNull(loanIdTransactionsResponse);
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
final Long transactionId = loanIdTransactionsResponse.getResourceId();
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.overpaid");
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00"));
GetDelinquencyRangesResponse delinquencyRange = getLoansLoanIdResponse.getDelinquencyRange();
assertNull(delinquencyRange);
log.info("Loan Delinquency Range is null {}", (delinquencyRange == null));
final Integer chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "50.00", 0,
final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "50.00", 0,
responseSpec);
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId, 0, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, chargebackTransactionId.intValue(), 0, Double.valueOf("0.00"));
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
@@ -396,16 +398,16 @@ public class LoanTransactionChargebackTest {
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
loanId);
assertNotNull(loanIdTransactionsResponse);
final Integer transactionId = loanIdTransactionsResponse.getResourceId();
final Long transactionId = loanIdTransactionsResponse.getResourceId();
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met");
// First round, empty array
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 0, Double.valueOf("0.00"));
loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "200.00", 0, responseSpec);
loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "200.00", 0, responseSpec);
Double expectedAmount = Double.valueOf("200.00");
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -416,9 +418,9 @@ public class LoanTransactionChargebackTest {
DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, expectedAmount);
// Second round, array size equal to 1
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 1, Double.valueOf("0.00"));
loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "300.00", 1, responseSpec);
loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "300.00", 1, responseSpec);
expectedAmount = Double.valueOf("500.00");
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -428,9 +430,9 @@ public class LoanTransactionChargebackTest {
DelinquencyBucketsHelper.evaluateLoanCollectionData(getLoansLoanIdResponse, 0, expectedAmount);
// Third round, array size equal to 2
reviewLoanTransactionRelations(loanId, transactionId, 2, Double.valueOf("0.00"));
reviewLoanTransactionRelations(loanId, transactionId.intValue(), 2, Double.valueOf("0.00"));
loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, "500.00", 0, responseSpec);
loanTransactionHelper.applyChargebackTransaction(loanId, transactionId.intValue(), "500.00", 0, responseSpec);
expectedAmount = Double.valueOf("1000.00");
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -23,13 +23,17 @@ import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.BusinessDateRequest;
import org.apache.fineract.client.models.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.integrationtests.client.IntegrationTest;
@Slf4j
public final class BusinessDateHelper {
public final class BusinessDateHelper extends IntegrationTest {
private BusinessDateHelper() {}
public BusinessDateHelper() {}
public static HashMap updateBusinessDate(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final BusinessDateType type, final LocalDate date) {
@@ -39,6 +43,20 @@ public final class BusinessDateHelper {
return Utils.performServerPost(requestSpec, responseSpec, BUSINESS_DATE_API, buildBusinessDateRequest(type, date), "changes");
}
public BusinessDateResponse updateBusinessDate(final BusinessDateRequest request) {
log.info("------------------UPDATE BUSINESS DATE----------------------");
log.info("------------------Type: {}, date: {}----------------------", request.getType(), request.getDate());
return ok(fineract().businessDateManagement.updateBusinessDate(request));
}
public BusinessDateResponse getBusinessDate(final String type) {
return ok(fineract().businessDateManagement.getBusinessDate(type));
}
public List<BusinessDateResponse> getBusinessDates() {
return ok(fineract().businessDateManagement.getBusinessDates());
}
private static String buildBusinessDateRequest(BusinessDateType type, LocalDate date) {
final HashMap<String, String> map = new HashMap<>();
map.put("type", type.name());
@@ -41,15 +41,17 @@ import org.apache.fineract.client.models.GetClientsClientIdResponse;
import org.apache.fineract.client.models.PostClientClientIdAddressesRequest;
import org.apache.fineract.client.models.PostClientClientIdAddressesResponse;
import org.apache.fineract.client.models.PostClientsRequest;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType;
import org.apache.fineract.integrationtests.client.IntegrationTest;
import org.apache.fineract.integrationtests.common.system.CodeHelper;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
@Slf4j
@RequiredArgsConstructor
public class ClientHelper {
public class ClientHelper extends IntegrationTest {
private static final String CREATE_CLIENT_URL = "/fineract-provider/api/v1/clients?" + Utils.TENANT_IDENTIFIER;
private static final String CLIENT_URL = "/fineract-provider/api/v1/clients";
@@ -77,6 +79,10 @@ public class ClientHelper {
return Utils.performServerPost(requestSpec, responseSpec, CREATE_CLIENT_URL, requestBody, "clientId");
}
public PostClientsResponse createClient(final PostClientsRequest request) {
return ok(fineract().clients.create6(request));
}
public static Integer createClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
return createClient(requestSpec, responseSpec, "04 March 2011");
}
@@ -425,6 +425,16 @@ public class ExternalEventConfigurationHelper {
shareProductDividentsCreateBusinessEvent.put("enabled", false);
defaults.add(shareProductDividentsCreateBusinessEvent);
Map<String, Object> loanChargeAdjustmentPostBusinessEvent = new HashMap<>();
loanChargeAdjustmentPostBusinessEvent.put("type", "LoanChargeAdjustmentPostBusinessEvent");
loanChargeAdjustmentPostBusinessEvent.put("enabled", false);
defaults.add(loanChargeAdjustmentPostBusinessEvent);
Map<String, Object> loanChargeAdjustmentPreBusinessEvent = new HashMap<>();
loanChargeAdjustmentPreBusinessEvent.put("type", "LoanChargeAdjustmentPreBusinessEvent");
loanChargeAdjustmentPreBusinessEvent.put("enabled", false);
defaults.add(loanChargeAdjustmentPreBusinessEvent);
return defaults;
}
@@ -22,10 +22,13 @@ import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.fineract.client.models.PostGLAccountsRequest;
import org.apache.fineract.client.models.PostGLAccountsResponse;
import org.apache.fineract.integrationtests.client.IntegrationTest;
import org.apache.fineract.integrationtests.common.Utils;
@SuppressWarnings("rawtypes")
public class AccountHelper {
public class AccountHelper extends IntegrationTest {
private static final String CREATE_GL_ACCOUNT_URL = "/fineract-provider/api/v1/glaccounts?" + Utils.TENANT_IDENTIFIER;
private static final String GL_ACCOUNT_ID_RESPONSE = "resourceId";
@@ -79,4 +82,8 @@ public class AccountHelper {
return accountRunningBalance;
}
public PostGLAccountsResponse createGLAccount(final PostGLAccountsRequest request) {
return ok(fineract().glAccounts.createGLAccount1(request));
}
}
@@ -23,8 +23,10 @@ import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.fineract.client.models.PostChargesRequest;
import org.apache.fineract.client.models.PostChargesResponse;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.client.IntegrationTest;
import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
@@ -32,9 +34,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings({ "rawtypes", "unchecked" })
public final class ChargesHelper {
public final class ChargesHelper extends IntegrationTest {
private ChargesHelper() {
public ChargesHelper() {
}
@@ -522,4 +524,8 @@ public final class ChargesHelper {
"status");
}
public PostChargesResponse createCharges(PostChargesRequest request) {
return ok(fineract().charges.createCharge(request));
}
}
@@ -91,6 +91,10 @@ public class LoanProductTestBuilder {
private String maxPrincipal = "10000000.00";
private Account[] accountList = null;
private List<Map<String, Long>> feeToIncomeAccountMappings = null;
private List<Map<String, Long>> penaltyToIncomeAccountMappings = null;
private Account feeAndPenaltyAssetAccount;
private Boolean multiDisburseLoan = false;
private final String outstandingLoanBalance = "35000";
private String maxTrancheCount = "3";
@@ -196,7 +200,7 @@ public class LoanProductTestBuilder {
}
if (this.accountingRule.equals(ACCRUAL_UPFRONT) || this.accountingRule.equals(ACCRUAL_PERIODIC)) {
map.putAll(getAccountMappingForAccrualBased());
map.putAll(getAccountMappingForAccrualBased(this.feeAndPenaltyAssetAccount));
} else if (this.accountingRule.equals(CASH_BASED)) {
map.putAll(getAccountMappingForCashBased());
}
@@ -250,6 +254,13 @@ public class LoanProductTestBuilder {
map.put("delinquencyBucketId", delinquencyBucketId);
}
if (this.feeToIncomeAccountMappings != null) {
map.put("feeToIncomeAccountMappings", this.feeToIncomeAccountMappings);
}
if (this.penaltyToIncomeAccountMappings != null) {
map.put("penaltyToIncomeAccountMappings", this.penaltyToIncomeAccountMappings);
}
return map;
}
@@ -436,7 +447,7 @@ public class LoanProductTestBuilder {
return map;
}
private Map<String, String> getAccountMappingForAccrualBased() {
private Map<String, String> getAccountMappingForAccrualBased(Account feeAndPenaltyAssetAccount) {
final Map<String, String> map = new HashMap<>();
for (int i = 0; i < this.accountList.length; i++) {
if (this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) {
@@ -444,9 +455,14 @@ public class LoanProductTestBuilder {
map.put("fundSourceAccountId", ID);
map.put("loanPortfolioAccountId", ID);
map.put("transfersInSuspenseAccountId", ID);
if (feeAndPenaltyAssetAccount != null) {
map.put("receivableFeeAccountId", feeAndPenaltyAssetAccount.getAccountID().toString());
map.put("receivablePenaltyAccountId", feeAndPenaltyAssetAccount.getAccountID().toString());
} else {
map.put("receivableFeeAccountId", ID);
map.put("receivablePenaltyAccountId", ID);
}
map.put("receivableInterestAccountId", ID);
map.put("receivableFeeAccountId", ID);
map.put("receivablePenaltyAccountId", ID);
}
if (this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) {
@@ -587,4 +603,30 @@ public class LoanProductTestBuilder {
return this;
}
public LoanProductTestBuilder withFeeToIncomeAccountMapping(final Long chargeId, final Long accountId) {
if (this.feeToIncomeAccountMappings == null) {
this.feeToIncomeAccountMappings = new ArrayList<>();
}
Map<String, Long> newMap = new HashMap<>();
newMap.put("chargeId", chargeId);
newMap.put("incomeAccountId", accountId);
this.feeToIncomeAccountMappings.add(newMap);
return this;
}
public LoanProductTestBuilder withPenaltyToIncomeAccountMapping(final Long chargeId, final Long accountId) {
if (this.penaltyToIncomeAccountMappings == null) {
this.penaltyToIncomeAccountMappings = new ArrayList<>();
}
Map<String, Long> newMap = new HashMap<>();
newMap.put("chargeId", chargeId);
newMap.put("incomeAccountId", accountId);
this.penaltyToIncomeAccountMappings.add(newMap);
return this;
}
public LoanProductTestBuilder withFeeAndPenaltyAssetAccount(final Account account) {
this.feeAndPenaltyAssetAccount = account;
return this;
}
}
@@ -51,12 +51,17 @@ import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdSummary;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.GetPaymentTypesResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.client.IntegrationTest;
import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
import org.apache.fineract.integrationtests.common.Utils;
@@ -65,11 +70,9 @@ import org.apache.poi.ss.usermodel.Workbook;
@Slf4j
@SuppressWarnings({ "rawtypes", "unchecked" })
public class LoanTransactionHelper {
private final RequestSpecification requestSpec;
private final ResponseSpecification responseSpec;
public class LoanTransactionHelper extends IntegrationTest {
public static final String DATE_TIME_FORMAT = "dd MMMM yyyy HH:mm";
private static final String CREATE_LOAN_PRODUCT_URL = "/fineract-provider/api/v1/loanproducts?" + Utils.TENANT_IDENTIFIER;
private static final String APPLY_LOAN_URL = "/fineract-provider/api/v1/loans?" + Utils.TENANT_IDENTIFIER;
private static final String LOAN_ACCOUNT_URL = "/fineract-provider/api/v1/loans";
@@ -90,10 +93,9 @@ public class LoanTransactionHelper {
private static final String RECOVER_FROM_GUARANTORS_COMMAND = "recoverGuarantees";
private static final String MAKE_REFUND_BY_CASH_COMMAND = "refundByCash";
private static final String FORECLOSURE_COMMAND = "foreclosure";
public static final String DATE_TIME_FORMAT = "dd MMMM yyyy HH:mm";
private static final Gson GSON = new JSON().getGson();
private final RequestSpecification requestSpec;
private final ResponseSpecification responseSpec;
public LoanTransactionHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
this.requestSpec = requestSpec;
@@ -221,7 +223,9 @@ public class LoanTransactionHelper {
final ResponseSpecification responseSpec, final Integer loanID) {
final String URL = "/fineract-provider/api/v1/loans/" + loanID + "/delinquencytags?" + Utils.TENANT_IDENTIFIER;
final String response = Utils.performServerGet(requestSpec, responseSpec, URL);
Type delinquencyTagsListType = new TypeToken<ArrayList<GetDelinquencyTagHistoryResponse>>() {}.getType();
Type delinquencyTagsListType = new TypeToken<ArrayList<GetDelinquencyTagHistoryResponse>>() {
}.getType();
return GSON.fromJson(response, delinquencyTagsListType);
}
@@ -461,11 +465,20 @@ public class LoanTransactionHelper {
return postLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID), getRepaymentBodyAsJSON(date, amountToBePaid));
}
public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "repayment"));
}
public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Integer loanId, final Integer transactionId, String date,
ResponseSpecification responseSpec) {
return postLoanTransaction(createLoanTransactionURL(UNDO, loanId, transactionId), getUndoJsonBody(date), responseSpec);
}
public PostLoansLoanIdTransactionsTransactionIdResponse reverseLoanTransaction(final Long loanId, final Long transactionId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction(loanId, transactionId, request, "undo"));
}
public HashMap makeRepaymentWithPDC(final String date, final Float amountToBePaid, final Integer loanID, final Integer paymentType) {
return (HashMap) performLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID),
getRepaymentWithPDCBodyAsJSON(date, amountToBePaid, paymentType), "");
@@ -540,6 +553,11 @@ public class LoanTransactionHelper {
return Utils.performServerPut(requestSpec, responseSpec, TRANSAC_URL, body, "");
}
public PostLoansLoanIdChargesChargeIdResponse chargeAdjustment(final Long loanId, final Long chargeId,
final PostLoansLoanIdChargesChargeIdRequest request) {
return ok(fineract().loanCharges.executeLoanCharge1(loanId, chargeId, request, "adjustment"));
}
public Integer undoWaiveChargesForLoanReturnResourceId(final Integer loanId, final Integer transactionId, final String body) {
log.info("--------------------------------- UNDO WAIVE CHARGES FOR LOAN --------------------------------");
final String TRANSAC_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/" + transactionId + "?"
@@ -575,6 +593,14 @@ public class LoanTransactionHelper {
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, param);
}
public GetLoansLoanIdTransactionsTransactionIdResponse getLoanTransactionDetails(final Long loanId, final Long transactionId) {
return ok(fineract().loanTransactions.retrieveTransaction(loanId, transactionId, null));
}
public GetLoansLoanIdResponse getLoanDetails(final Long loanId) {
return ok(fineract().loans.retrieveLoan(loanId, false, "all", null, null));
}
public GetLoansLoanIdTransactionsTransactionIdResponse getLoanTransaction(final Integer loanId, final Integer txnId) {
final String GET_LOAN_CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/" + txnId + "?"
+ Utils.TENANT_IDENTIFIER;
@@ -1203,7 +1229,7 @@ public class LoanTransactionHelper {
}
}
public Integer applyChargebackTransaction(final Integer loanId, final Integer transactionId, final String amount,
public Long applyChargebackTransaction(final Integer loanId, final Integer transactionId, final String amount,
final Integer paymentTypeIdx, ResponseSpecification responseSpec) {
List<GetPaymentTypesResponse> paymentTypeList = PaymentTypeHelper.getSystemPaymentType(this.requestSpec, this.responseSpec);
assertTrue(!paymentTypeList.isEmpty());