You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by al...@apache.org on 2022/09/30 19:22:30 UTC
[fineract] branch develop updated: Chargeback Loan Transaction
This is an automated email from the ASF dual-hosted git repository.
aleks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 40b6eb50f Chargeback Loan Transaction
40b6eb50f is described below
commit 40b6eb50f6b17b3f5bd00512bfc883384c83843d
Author: Jose Alberto Hernandez <al...@MacBook-Pro.local>
AuthorDate: Sun Sep 18 18:33:41 2022 -0500
Chargeback Loan Transaction
---
.../main/avro/payment/v1/PaymentTypeDataV1.avsc | 16 +++
.../commands/service/CommandWrapperBuilder.java | 9 ++
.../LoanChargebackTransactionBusinessEvent.java | 40 ++++++
.../interoperation/service/InteropServiceImpl.java | 4 +-
.../service/NotificationDomainServiceImpl.java | 17 +++
.../fineract/portfolio/charge/domain/Charge.java | 2 +-
.../loanaccount/api/LoanApiConstants.java | 5 +
.../api/LoanTransactionsApiResource.java | 11 +-
.../loanaccount/data/LoanTransactionData.java | 6 +
.../data/LoanTransactionRelationData.java} | 23 ++--
.../portfolio/loanaccount/domain/Loan.java | 18 ++-
.../portfolio/loanaccount/domain/LoanEvent.java | 3 +-
.../loanaccount/domain/LoanTransaction.java | 24 +++-
.../domain/LoanTransactionRelation.java | 65 ++++++++++
.../domain/LoanTransactionRelationRepository.java} | 9 +-
.../domain/LoanTransactionRelationTypeEnum.java | 54 ++++++++
.../loanaccount/domain/LoanTransactionType.java | 5 +
.../LoanRepaymentChargebackCommandHandler.java | 43 +++++++
.../mapper/LoanTransactionRelationMapper.java | 39 ++++++
.../serialization/LoanEventApiJsonValidator.java | 44 ++++++-
.../service/LoanReadPlatformService.java | 4 +
.../service/LoanReadPlatformServiceImpl.java | 100 +++++++--------
.../service/LoanWritePlatformService.java | 2 +
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 104 +++++++++++++++-
.../paymentdetail/domain/PaymentDetail.java | 2 +-
.../paymenttype/api/PaymentTypeApiResource.java | 31 ++---
.../api/PaymentTypeApiResourceConstants.java | 2 +
.../api/PaymentTypeApiResourceSwagger.java | 16 +++
.../paymenttype/data/PaymentTypeData.java | 14 ++-
.../paymenttype/data/PaymentTypeDataValidator.java | 34 ++++-
.../portfolio/paymenttype/domain/PaymentType.java | 34 ++---
.../paymenttype/domain/PaymentTypeRepository.java | 5 +
.../domain/PaymentTypeRepositoryWrapper.java | 13 +-
.../PaymentTypeMapper.java} | 20 ++-
.../service/PaymentTypeReadPlatformService.java | 2 +
.../PaymentTypeReadPlatformServiceImpl.java | 58 +++------
.../service/PaymentTypeWriteServiceImpl.java | 19 ++-
.../portfolio/savings/domain/SavingsAccount.java | 4 +-
.../SavingsAccountReadPlatformServiceImpl.java | 3 +-
.../db/changelog/tenant/changelog-tenant.xml | 1 +
.../parts/0052_loan_transaction_chargeback.xml | 134 ++++++++++++++++++++
.../LoanTransactionChargebackTest.java | 138 +++++++++++++++++++++
.../integrationtests/common/PaymentTypeHelper.java | 13 +-
.../common/loans/LoanTransactionHelper.java | 9 ++
44 files changed, 1006 insertions(+), 193 deletions(-)
diff --git a/fineract-avro-schemas/src/main/avro/payment/v1/PaymentTypeDataV1.avsc b/fineract-avro-schemas/src/main/avro/payment/v1/PaymentTypeDataV1.avsc
index a1a02919c..0c96a5b7e 100644
--- a/fineract-avro-schemas/src/main/avro/payment/v1/PaymentTypeDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/payment/v1/PaymentTypeDataV1.avsc
@@ -42,6 +42,22 @@
"null",
"long"
]
+ },
+ {
+ "default": null,
+ "name": "codeName",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ {
+ "default": null,
+ "name": "isSystemDefined",
+ "type": [
+ "null",
+ "boolean"
+ ]
}
]
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 952da0cec..28d523242 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -905,6 +905,15 @@ public class CommandWrapperBuilder {
return this;
}
+ public CommandWrapperBuilder chargebackTransaction(final Long loanId, final Long transactionId) {
+ this.actionName = "CHARGEBACK";
+ this.entityName = "LOAN";
+ this.entityId = transactionId;
+ this.loanId = loanId;
+ this.href = "/loans/" + loanId + "/transactions/" + transactionId;
+ return this;
+ }
+
public CommandWrapperBuilder loanForeclosure(final Long loanId) {
this.actionName = "FORECLOSURE";
this.entityName = "LOAN";
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanChargebackTransactionBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanChargebackTransactionBusinessEvent.java
new file mode 100644
index 000000000..6cc63c795
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanChargebackTransactionBusinessEvent.java
@@ -0,0 +1,40 @@
+/**
+ * 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;
+
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+
+public class LoanChargebackTransactionBusinessEvent extends LoanTransactionBusinessEvent {
+
+ public static final String LOAN_CHARGEBACK_TRANSACTION_BUSINESS_EVENT = "LoanChargebackTransactionBusinessEvent";
+ public static final String LOAN_CHARGEBACK_TRANSACTION_PERMISSION = "LOAN_CHARGEBACK";
+ public static final String LOAN_CHARGEBACK_TRANSACTION_OBJECT_TYPE = "loanTransaction";
+ public static final String LOAN_CHARGEBACK_TRANSACTION_EVENT_TYPE = "loanTransactionChargeback";
+ public static final String LOAN_CHARGEBACK_TRANSACTION_NOTIFICATION = "Loan Transaction has been chargeback";
+
+ public LoanChargebackTransactionBusinessEvent(LoanTransaction transactionToChargeback) {
+ super(transactionToChargeback);
+ }
+
+ @Override
+ public String getType() {
+ return LOAN_CHARGEBACK_TRANSACTION_BUSINESS_EVENT;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
index 81691513c..c045c09aa 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
@@ -332,7 +332,7 @@ public class InteropServiceImpl implements InteropService {
InteropTransactionRequestData request = dataValidator.validateAndParseCreateRequest(command);
// TODO: error handling
- SavingsAccount savingsAccount = validateAndGetSavingAccount(request);
+ validateAndGetSavingAccount(request);
return InteropTransactionRequestResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED,
request.getExpiration(), request.getExtensionList(), request.getRequestCode());
@@ -617,7 +617,7 @@ public class InteropServiceImpl implements InteropService {
PaymentType findPaymentType() {
List<PaymentType> paymentTypes = paymentTypeRepository.findAll();
for (PaymentType paymentType : paymentTypes) {
- if (!paymentType.isCashPayment()) {
+ if (!paymentType.getIsCashPayment()) {
return paymentType;
}
// TODO: for now first not cash is retured:
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
index e68eaeb30..9e607f73e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
@@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.event.business.domain.deposit.Recurrin
import org.apache.fineract.infrastructure.event.business.domain.group.CentersCreateBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.group.GroupsCreateBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanApprovedBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.LoanChargebackTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseAsRescheduleBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCreatedBusinessEvent;
@@ -53,6 +54,7 @@ import org.apache.fineract.notification.data.NotificationData;
import org.apache.fineract.notification.eventandlistener.NotificationEventPublisher;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.domain.FixedDepositAccount;
@@ -92,6 +94,8 @@ public class NotificationDomainServiceImpl implements NotificationDomainService
businessEventNotifierService.addPostBusinessEventListener(LoanCreatedBusinessEvent.class, new LoanCreatedListener());
businessEventNotifierService.addPostBusinessEventListener(LoanApprovedBusinessEvent.class, new LoanApprovedListener());
businessEventNotifierService.addPostBusinessEventListener(LoanCloseBusinessEvent.class, new LoanClosedListener());
+ businessEventNotifierService.addPostBusinessEventListener(LoanChargebackTransactionBusinessEvent.class,
+ new LoanChargebackTransactionListener());
businessEventNotifierService.addPostBusinessEventListener(LoanCloseAsRescheduleBusinessEvent.class,
new LoanCloseAsRescheduledListener());
businessEventNotifierService.addPostBusinessEventListener(LoanTransactionMakeRepaymentPostBusinessEvent.class,
@@ -247,6 +251,19 @@ public class NotificationDomainServiceImpl implements NotificationDomainService
}
}
+ private class LoanChargebackTransactionListener implements BusinessEventListener<LoanChargebackTransactionBusinessEvent> {
+
+ @Override
+ public void onBusinessEvent(LoanChargebackTransactionBusinessEvent event) {
+ LoanTransaction loanTransaction = event.get();
+ buildNotification(LoanChargebackTransactionBusinessEvent.LOAN_CHARGEBACK_TRANSACTION_PERMISSION,
+ LoanChargebackTransactionBusinessEvent.LOAN_CHARGEBACK_TRANSACTION_OBJECT_TYPE, loanTransaction.getId(),
+ LoanChargebackTransactionBusinessEvent.LOAN_CHARGEBACK_TRANSACTION_NOTIFICATION,
+ LoanChargebackTransactionBusinessEvent.LOAN_CHARGEBACK_TRANSACTION_EVENT_TYPE, context.authenticatedUser().getId(),
+ loanTransaction.getLoan().getOfficeId());
+ }
+ }
+
private class LoanMakeRepaymentListener implements BusinessEventListener<LoanTransactionMakeRepaymentPostBusinessEvent> {
@Override
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
index 8d2bf69d8..45270e9d1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
@@ -670,7 +670,7 @@ public class Charge extends AbstractPersistableCustom {
PaymentTypeData paymentTypeData = null;
if (this.paymentType != null) {
- paymentTypeData = PaymentTypeData.instance(paymentType.getId(), paymentType.getPaymentName());
+ paymentTypeData = PaymentTypeData.instance(paymentType.getId(), paymentType.getName());
}
final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index 47eb7d89e..5c3281ee3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -150,4 +150,9 @@ public interface LoanApiConstants {
String DISALLOW_EXPECTED_DISBURSEMENTS = "disallowExpectedDisbursements";
+ String TRANSACTION_AMOUNT_PARAMNAME = "transactionAmount";
+ String PAYMENT_TYPE_PARAMNAME = "paymentTypeId";
+
+ // Commands
+ String CHARGEBACK_TRANSACTION_COMMAND = "chargeback";
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 62bc82943..a15b976a2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -275,10 +275,17 @@ public class LoanTransactionsApiResource {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdResponse.class))) })
public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId,
- @Parameter(hidden = true) final String apiRequestBodyAsJson) {
+ @Parameter(hidden = true) final String apiRequestBodyAsJson,
+ @QueryParam("command") @Parameter(description = "command") final String commandParam) {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
- final CommandWrapper commandRequest = builder.adjustTransaction(loanId, transactionId).build();
+
+ CommandWrapper commandRequest = null;
+ if (CommandParameterUtil.is(commandParam, LoanApiConstants.CHARGEBACK_TRANSACTION_COMMAND)) {
+ commandRequest = builder.chargebackTransaction(loanId, transactionId).build();
+ } else { // Default to adjust the Loan Transaction
+ commandRequest = builder.adjustTransaction(loanId, transactionId).build();
+ }
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
index 1d1006855..e32065d36 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
@@ -91,6 +91,8 @@ public class LoanTransactionData {
private String reversalExternalId;
private LocalDate reversedOnDate;
+ private List<LoanTransactionRelationData> transactionRelations;
+
public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
Integer rowIndex, String locale, String dateFormat) {
return new LoanTransactionData(repaymentAmount, lastRepaymentDate, repaymentTypeId, rowIndex, locale, dateFormat);
@@ -348,4 +350,8 @@ public class LoanTransactionData {
public void setLoanChargePaidByList(Collection<LoanChargePaidByData> loanChargePaidByList) {
this.loanChargePaidByList = loanChargePaidByList;
}
+
+ public void setLoanTransactionRelations(List<LoanTransactionRelationData> transactionRelations) {
+ this.transactionRelations = transactionRelations;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionRelationData.java
similarity index 58%
copy from fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
copy to fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionRelationData.java
index b3aead6d3..aa848ed72 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionRelationData.java
@@ -16,15 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.portfolio.paymenttype.service;
+package org.apache.fineract.portfolio.loanaccount.data;
-import java.util.Collection;
-import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
-public interface PaymentTypeReadPlatformService {
+@Getter
+@Setter
+@AllArgsConstructor
+public class LoanTransactionRelationData implements Serializable {
- Collection<PaymentTypeData> retrieveAllPaymentTypes();
-
- PaymentTypeData retrieveOne(Long paymentTypeId);
+ private Long fromLoanTransaction;
+ private Long toLoanTransaction;
+ private LoanTransactionRelationTypeEnum relationType;
+ private BigDecimal amount;
+ private String paymentType;
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index f3d72a97d..d70c6ab28 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -1338,7 +1338,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
this.summary.updateSummary(loanCurrency(), principal, getRepaymentScheduleInstallments(), this.loanSummaryWrapper,
isDisbursed(), this.charges);
- updateLoanOutstandingBalaces();
+ updateLoanOutstandingBalances();
}
}
@@ -2850,7 +2850,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
chargesPayment.updateLoan(this);
addLoanTransaction(chargesPayment);
- updateLoanOutstandingBalaces();
+ updateLoanOutstandingBalances();
}
if (getApprovedOnDate() != null && disbursedOn.isBefore(getApprovedOnDate())) {
@@ -2897,7 +2897,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
chargesPayment.updateComponents(zero, zero, charge.getAmount(getCurrency()), zero);
chargesPayment.updateLoan(this);
addLoanTransaction(chargesPayment);
- updateLoanOutstandingBalaces();
+ updateLoanOutstandingBalances();
charge.markAsFullyPaid();
return chargesPayment;
}
@@ -3426,7 +3426,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
addLoanTransaction(finalAccrual);
}
}
- updateLoanOutstandingBalaces();
+ updateLoanOutstandingBalances();
}
private void determineCumulativeIncomeFromInstallments(HashMap<String, BigDecimal> cumulativeIncomeFromInstallments) {
@@ -3898,6 +3898,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return changedTransactionDetail;
}
+ public void handleChargebackTransaction(LoanTransaction chargebackTransaction,
+ final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+ chargebackTransaction.updateLoan(this);
+ // TODO To be updated in the repayment schedule
+ }
+
/**
* Behaviour added to comply with capability of previous mifos product to support easier transition to fineract
* platform.
@@ -5422,7 +5428,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
addLoanTransaction(accrual);
}
}
- updateLoanOutstandingBalaces();
+ updateLoanOutstandingBalances();
}
private void determineFeeDetails(LocalDate fromDate, LocalDate toDate, HashMap<String, Object> feeDetails) {
@@ -5665,7 +5671,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return interestRecalculatedOn;
}
- private void updateLoanOutstandingBalaces() {
+ private void updateLoanOutstandingBalances() {
Money outstanding = Money.zero(getCurrency());
List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals();
for (LoanTransaction loanTransaction : loanTransactions) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
index 9d7f1dfba..e0c11e21f 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
@@ -48,5 +48,6 @@ public enum LoanEvent {
LOAN_INITIATE_TRANSFER, //
LOAN_REJECT_TRANSFER, //
LOAN_WITHDRAW_TRANSFER, //
- LOAN_CREDIT_BALANCE_REFUND;
+ LOAN_CREDIT_BALANCE_REFUND, //
+ LOAN_CHARGEBACK;
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index a4d5d3e00..22313cc2b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -130,6 +130,9 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanTransaction")
private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>();
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "fromTransaction")
+ private Set<LoanTransactionRelation> loanTransactionRelations = new HashSet<>();
+
protected LoanTransaction() {}
public static LoanTransaction incomePosting(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
@@ -155,6 +158,14 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId);
}
+ public static LoanTransaction chargeback(final Loan loan, final Money amount, final PaymentDetail paymentDetail,
+ final LocalDate paymentDate, final String externalId) {
+ LoanTransaction loanTransaction = new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGEBACK, paymentDetail,
+ amount.getAmount(), paymentDate, externalId);
+ loanTransaction.principalPortion = amount.getAmount();
+ return loanTransaction;
+ }
+
public static LoanTransaction repaymentType(final LoanTransactionType repaymentType, final Office office, final Money amount,
final PaymentDetail paymentDetail, final LocalDate paymentDate, final String externalId, final String chargeRefundChargeType) {
return new LoanTransaction(null, office, repaymentType, paymentDetail, amount.getAmount(), paymentDate, externalId,
@@ -170,7 +181,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
break;
}
}
-
}
public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
@@ -589,6 +599,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return getTypeOf().isChargePayment() && isNotReversed();
}
+ public boolean isChargeback() {
+ return getTypeOf().isChargeback() && isNotReversed();
+ }
+
public boolean isPenaltyPayment() {
boolean isPenalty = false;
if (isChargePayment()) {
@@ -849,6 +863,14 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return submittedOnDate;
}
+ public Set<LoanTransactionRelation> getLoanTransactionRelations() {
+ return loanTransactionRelations;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
// TODO missing hashCode(), equals(Object obj), but probably OK as long as
// this is never stored in a Collection.
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
new file mode 100644
index 000000000..07cc3a57c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
@@ -0,0 +1,65 @@
+/**
+ * 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.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.JoinColumn;
+import javax.persistence.Table;
+import javax.persistence.Version;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "m_loan_transaction_relation")
+public class LoanTransactionRelation extends AbstractAuditableWithUTCDateTimeCustom {
+
+ @JoinColumn(name = "from_loan_transaction_id", nullable = false)
+ private LoanTransaction fromTransaction;
+
+ @JoinColumn(name = "to_loan_transaction_id", nullable = false)
+ private LoanTransaction toTransaction;
+
+ @Enumerated(EnumType.ORDINAL)
+ @Column(name = "relation_type_enum", nullable = false)
+ private LoanTransactionRelationTypeEnum relationType;
+
+ @Version
+ private Long version;
+
+ protected LoanTransactionRelation() {}
+
+ protected LoanTransactionRelation(@NotNull LoanTransaction fromTransaction, @NotNull LoanTransaction toTransaction,
+ LoanTransactionRelationTypeEnum relationType) {
+ this.fromTransaction = fromTransaction;
+ this.toTransaction = toTransaction;
+ this.relationType = relationType;
+ }
+
+ public static LoanTransactionRelation instance(@NotNull LoanTransaction fromTransaction, @NotNull LoanTransaction toTransaction,
+ LoanTransactionRelationTypeEnum relation) {
+ return new LoanTransactionRelation(fromTransaction, toTransaction, relation);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationRepository.java
similarity index 69%
copy from fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
copy to fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationRepository.java
index d4119bdf2..6eeec08ea 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationRepository.java
@@ -16,11 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.portfolio.paymenttype.domain;
+package org.apache.fineract.portfolio.loanaccount.domain;
+import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-public interface PaymentTypeRepository extends JpaRepository<PaymentType, Long>, JpaSpecificationExecutor<PaymentType> {
+public interface LoanTransactionRelationRepository
+ extends JpaRepository<LoanTransactionRelation, Long>, JpaSpecificationExecutor<LoanTransactionRelation> {
+
+ List<LoanTransactionRelation> findByFromTransactionAndRelationType(LoanTransaction loanTransaction,
+ LoanTransactionRelationTypeEnum relationType);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationTypeEnum.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationTypeEnum.java
new file mode 100644
index 000000000..d07987798
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationTypeEnum.java
@@ -0,0 +1,54 @@
+/**
+ * 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.domain;
+
+public enum LoanTransactionRelationTypeEnum {
+
+ INVALID(0, "loanTransactionType.invalid"), //
+ CHARGEBACK(1, "loanTransactionRelationType.chargeback");
+
+ private final Integer value;
+ private final String code;
+
+ LoanTransactionRelationTypeEnum(final Integer value, final String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public Integer getValue() {
+ return this.value;
+ }
+
+ public String getCode() {
+ return this.code;
+ }
+
+ public static LoanTransactionRelationTypeEnum fromInt(final Integer transactionType) {
+
+ if (transactionType == null) {
+ return LoanTransactionRelationTypeEnum.INVALID;
+ }
+
+ return switch (transactionType) {
+ case 1 -> LoanTransactionRelationTypeEnum.CHARGEBACK;
+ default -> LoanTransactionRelationTypeEnum.INVALID;
+ };
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index 1515d656e..7fee78b88 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -173,4 +173,9 @@ public enum LoanTransactionType {
public boolean isIncomePosting() {
return this.value.equals(LoanTransactionType.INCOME_POSTING.getValue());
}
+
+ public boolean isChargeback() {
+ return this.value.equals(LoanTransactionType.CHARGEBACK.getValue());
+ }
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentChargebackCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentChargebackCommandHandler.java
new file mode 100644
index 000000000..58991fac3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentChargebackCommandHandler.java
@@ -0,0 +1,43 @@
+/**
+ * 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.LoanWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "LOAN", action = "CHARGEBACK")
+public class LoanRepaymentChargebackCommandHandler implements NewCommandSourceHandler {
+
+ private final LoanWritePlatformService writePlatformService;
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.chargebackLoanTransaction(command.getLoanId(), command.entityId(), command);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionRelationMapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionRelationMapper.java
new file mode 100644
index 000000000..37f524e50
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionRelationMapper.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.mapper;
+
+import java.util.List;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface LoanTransactionRelationMapper {
+
+ @Mapping(target = "fromLoanTransaction", source = "source.fromTransaction.id")
+ @Mapping(target = "toLoanTransaction", source = "source.toTransaction.id")
+ @Mapping(target = "relationType", source = "source.relationType")
+ @Mapping(target = "amount", source = "source.toTransaction.amount")
+ @Mapping(target = "paymentType", source = "source.toTransaction.paymentDetail.paymentType.name")
+ LoanTransactionRelationData map(LoanTransactionRelation source);
+
+ List<LoanTransactionRelationData> map(List<LoanTransactionRelation> sources);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
index 1e0ac6c90..1da8a069f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
@@ -212,7 +212,37 @@ public final class LoanEventApiJsonValidator {
baseDataValidator.reset().parameter(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME).ignoreIfNull().value(reversalExternalId)
.notExceedingLengthOf(100);
- validatePaymentDetails(baseDataValidator, element);
+ validatePaymentDetails(baseDataValidator, element, false);
+ throwExceptionIfValidationWarningsExist(dataValidationErrors);
+ }
+
+ public void validateChargebackTransaction(final String json) {
+
+ if (StringUtils.isBlank(json)) {
+ throw new InvalidJsonException();
+ }
+
+ final Set<String> transactionParameters = new HashSet<>(Arrays.asList(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME,
+ LoanApiConstants.localeParameterName, LoanApiConstants.externalIdParameterName, LoanApiConstants.noteParameterName,
+ LoanApiConstants.PAYMENT_TYPE_PARAMNAME));
+
+ 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.transaction");
+
+ final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+ final BigDecimal transactionAmount = this.fromApiJsonHelper
+ .extractBigDecimalWithLocaleNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME, element);
+ baseDataValidator.reset().parameter(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME).value(transactionAmount).notNull()
+ .zeroOrPositiveAmount();
+
+ final String note = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParameterName, element);
+ baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000);
+
+ validatePaymentDetails(baseDataValidator, element, true);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -262,9 +292,19 @@ public final class LoanEventApiJsonValidator {
}
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+ final boolean paymentDetailRequired = false; // Default value for backward compatibility
+ validatePaymentDetails(baseDataValidator, element, paymentDetailRequired);
+ }
+
+ private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element,
+ final boolean paymentDetailRequired) {
// Validate all string payment detail fields for max length
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
- baseDataValidator.reset().parameter("paymentTypeId").value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
+ if (paymentDetailRequired) {
+ baseDataValidator.reset().parameter("paymentTypeId").value(paymentTypeId).notNull().integerGreaterThanZero();
+ } else {
+ baseDataValidator.reset().parameter("paymentTypeId").value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
+ }
final Set<String> paymentDetailParameters = new HashSet<>(
Arrays.asList("accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
for (final String paymentDetailParameterName : paymentDetailParameters) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
index f52f20163..b47ff7547 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
@@ -36,6 +36,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -157,4 +158,7 @@ public interface LoanReadPlatformService {
List<LoanRepaymentScheduleInstallmentData> getRepaymentDataResponse(Long loanId);
CollectionData retrieveLoanCollectionData(Long loanId);
+
+ List<LoanTransactionRelationData> retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId);
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 9c0d3266e..7005b8adb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
+import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
@@ -97,6 +98,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
@@ -108,12 +110,17 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
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;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionRelationMapper;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
@@ -125,7 +132,6 @@ import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
import org.apache.fineract.useradministration.domain.AppUser;
import org.jetbrains.annotations.NotNull;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -136,6 +142,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
+@AllArgsConstructor
@Service
@Transactional(readOnly = true)
public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@@ -154,7 +161,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final CalendarReadPlatformService calendarReadPlatformService;
private final StaffReadPlatformService staffReadPlatformService;
private final PaginationHelper paginationHelper;
- private final LoanMapper loanMapper;
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
@@ -165,48 +171,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final ColumnValidator columnValidator;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
-
- @Autowired
- public LoanReadPlatformServiceImpl(final PlatformSecurityContext context,
- final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
- final LoanProductReadPlatformService loanProductReadPlatformService, final ClientReadPlatformService clientReadPlatformService,
- final GroupReadPlatformService groupReadPlatformService, final LoanDropdownReadPlatformService loanDropdownReadPlatformService,
- final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService,
- final CodeValueReadPlatformService codeValueReadPlatformService, final JdbcTemplate jdbcTemplate,
- final NamedParameterJdbcTemplate namedParameterJdbcTemplate, final CalendarReadPlatformService calendarReadPlatformService,
- final StaffReadPlatformService staffReadPlatformService, final PaymentTypeReadPlatformService paymentTypeReadPlatformService,
- final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
- final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService,
- final ConfigurationDomainService configurationDomainService,
- final DelinquencyReadPlatformService delinquencyReadPlatformService,
- final AccountDetailsReadPlatformService accountDetailsReadPlatformService, final LoanRepositoryWrapper loanRepositoryWrapper,
- final ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
- this.context = context;
- this.loanRepositoryWrapper = loanRepositoryWrapper;
- this.applicationCurrencyRepository = applicationCurrencyRepository;
- this.loanProductReadPlatformService = loanProductReadPlatformService;
- this.clientReadPlatformService = clientReadPlatformService;
- this.groupReadPlatformService = groupReadPlatformService;
- this.loanDropdownReadPlatformService = loanDropdownReadPlatformService;
- this.fundReadPlatformService = fundReadPlatformService;
- this.chargeReadPlatformService = chargeReadPlatformService;
- this.codeValueReadPlatformService = codeValueReadPlatformService;
- this.calendarReadPlatformService = calendarReadPlatformService;
- this.staffReadPlatformService = staffReadPlatformService;
- this.jdbcTemplate = jdbcTemplate;
- this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
- this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
- this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
- this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
- this.loanUtilService = loanUtilService;
- this.configurationDomainService = configurationDomainService;
- this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
- this.columnValidator = columnValidator;
- this.loanMapper = new LoanMapper(sqlGenerator, delinquencyReadPlatformService);
- this.sqlGenerator = sqlGenerator;
- this.paginationHelper = paginationHelper;
- this.delinquencyReadPlatformService = delinquencyReadPlatformService;
- }
+ private final LoanTransactionRepository loanTransactionRepository;
+ private final LoanTransactionRelationRepository loanTransactionRelationRepository;
+ private final LoanTransactionRelationMapper loanTransactionRelationMapper;
@Override
public LoanAccountData retrieveOne(final Long loanId) {
@@ -296,9 +263,16 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
/***
* TODO Vishwas: Remove references to "Contra" from the codebase
***/
- 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 ";
- return this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR
+ 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
+ for (LoanTransactionData loanTransaction : loanTransactionData) {
+ if (loanTransaction.getType().isRepaymentType()) {
+ loanTransaction
+ .setLoanTransactionRelations(this.retrieveLoanTransactionRelationsByLoanTransactionId(loanTransaction.getId()));
+ }
+ }
+ return loanTransactionData;
} catch (final EmptyResultDataAccessException e) {
return null;
}
@@ -310,10 +284,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final AppUser currentUser = this.context.authenticatedUser();
final String hierarchy = currentUser.getOffice().getHierarchy();
final String hierarchySearchString = hierarchy + "%";
+ final LoanMapper loanMapper = new LoanMapper(sqlGenerator, delinquencyReadPlatformService);
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
- sqlBuilder.append(this.loanMapper.loanSchema());
+ sqlBuilder.append(loanMapper.loanSchema());
// TODO - for time being this will data scope list of loans returned to
// only loans that have a client associated.
@@ -377,7 +352,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
final Object[] objectArray = extraCriterias.toArray();
final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
- return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.loanMapper);
+ return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, loanMapper);
}
@Override
@@ -588,7 +563,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
try {
final LoanTransactionsMapper rm = new LoanTransactionsMapper(sqlGenerator);
final String sql = "select " + rm.loanPaymentsSchema() + " where l.id = ? and tr.id = ? ";
- return this.jdbcTemplate.queryForObject(sql, rm, loanId, transactionId); // NOSONAR
+ LoanTransactionData loanTransactionData = this.jdbcTemplate.queryForObject(sql, rm, loanId, transactionId); // NOSONAR
+ loanTransactionData.setLoanTransactionRelations(this.retrieveLoanTransactionRelationsByLoanTransactionId(transactionId));
+ return loanTransactionData;
} catch (final EmptyResultDataAccessException e) {
throw new LoanTransactionNotFoundException(transactionId, e);
}
@@ -1310,10 +1287,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
public String loanPaymentsSchema() {
return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as " + sqlGenerator.escape("date")
- + ", tr.amount as total, " + " tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
- + " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, "
+ + ", tr.amount as total, tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
+ + " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, "
+ " tr.overpayment_portion_derived as overpayment, tr.outstanding_loan_balance_derived as outstandingLoanBalance, "
- + " tr.unrecognized_income_portion as unrecognizedIncome," + " tr.submitted_on_date as submittedOnDate, "
+ + " tr.unrecognized_income_portion as unrecognizedIncome, tr.submitted_on_date as submittedOnDate, "
+ " tr.manually_adjusted_or_reversed as manuallyReversed, tr.reversal_external_id as reversalExternalId, tr.reversed_on_date as reversedOnDate, "
+ " pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "
+ " pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, l.net_disbursal_amount as netDisbursalAmount,"
@@ -1323,10 +1300,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " pt.value as paymentTypeName, tr.external_id as externalId, tr.office_id as officeId, office.name as officeName, "
+ " fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,"
+ " fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,"
- + " fromtran.description as fromTransferDescription,"
- + " totran.id as toTransferId, totran.is_reversed as toTransferReversed,"
+ + " fromtran.description as fromTransferDescription, "
+ + " totran.id as toTransferId, totran.is_reversed as toTransferReversed, "
+ " totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,"
- + " totran.description as toTransferDescription " + " from m_loan l join m_loan_transaction tr on tr.loan_id = l.id"
+ + " totran.description as toTransferDescription from m_loan l join m_loan_transaction tr on tr.loan_id = l.id "
+ " join m_currency rc on rc." + sqlGenerator.escape("code") + " = l.currency_code "
+ " left JOIN m_payment_detail pd ON tr.payment_detail_id = pd.id"
+ " left join m_payment_type pt on pd.payment_type_id = pt.id" + " left join m_office office on office.id=tr.office_id"
@@ -1405,6 +1382,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
transfer = AccountTransferData.transferBasicDetails(toTransferId, currencyData, toTransferAmount, toTransferDate,
toTransferDescription, toTransferReversed);
}
+
return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currencyData, date, totalAmount,
netDisbursalAmount, principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion,
unrecognizedIncomePortion, externalId, transfer, null, outstandingLoanBalance, submittedOnDate, manuallyReversed,
@@ -2066,7 +2044,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
public String schema() {
return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as " + sqlGenerator.escape("date")
- + ", tr.amount as total, " + " tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
+ + ", tr.amount as total, tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
+ " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, "
+ " tr.overpayment_portion_derived as overpayment, tr.outstanding_loan_balance_derived as outstandingLoanBalance, "
+ " tr.unrecognized_income_portion as unrecognizedIncome " + " from m_loan_transaction tr ";
@@ -2492,4 +2470,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
delinquentAmount, lastPaymentDate, lastPaymentAmount);
}
}
+
+ @Override
+ public List<LoanTransactionRelationData> retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId) {
+ final LoanTransaction loanTransaction = this.loanTransactionRepository.getReferenceById(loanTransactionId);
+ List<LoanTransactionRelation> loanTransactionRelations = this.loanTransactionRelationRepository
+ .findByFromTransactionAndRelationType(loanTransaction, LoanTransactionRelationTypeEnum.CHARGEBACK);
+ return loanTransactionRelationMapper.map(loanTransactionRelations);
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
index d8e8a29c1..fb419edd7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
@@ -50,6 +50,8 @@ public interface LoanWritePlatformService {
CommandProcessingResult adjustLoanTransaction(Long loanId, Long transactionId, JsonCommand command);
+ CommandProcessingResult chargebackLoanTransaction(Long loanId, Long transactionId, JsonCommand command);
+
CommandProcessingResult waiveInterestOnLoan(Long loanId, JsonCommand command);
CommandProcessingResult writeOff(Long loanId, JsonCommand command);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 7115b2d90..7d010b191 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -59,6 +59,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAcceptT
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanApplyOverdueChargeBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.LoanChargebackTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseAsRescheduleBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDisbursalBusinessEvent;
@@ -180,6 +181,9 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
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.DateMismatchException;
@@ -237,6 +241,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanAccountDomainService loanAccountDomainService;
private final NoteRepository noteRepository;
private final LoanTransactionRepository loanTransactionRepository;
+ private final LoanTransactionRelationRepository loanTransactionRelationRepository;
private final LoanAssembler loanAssembler;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
@@ -392,7 +397,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> changes = new LinkedHashMap<>();
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
- if (paymentDetail != null && paymentDetail.getPaymentType() != null && paymentDetail.getPaymentType().isCashPayment()) {
+ if (paymentDetail != null && paymentDetail.getPaymentType() != null && paymentDetail.getPaymentType().getIsCashPayment()) {
BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
this.cashierTransactionDataValidator.validateOnLoanDisbursal(currentUser, loan.getCurrencyCode(), transactionAmount);
}
@@ -1184,6 +1189,103 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.with(changes).build();
}
+ @Transactional
+ @Override
+ public CommandProcessingResult chargebackLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) {
+ this.loanEventApiJsonValidator.validateChargebackTransaction(command.json());
+
+ Loan loan = this.loanAssembler.assembleFrom(loanId);
+ if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) {
+ throw new PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed",
+ "Loan transaction:" + transactionId + " chargeback not allowed as it involves in account transfer", transactionId);
+ }
+ if (loan.isClosedWrittenOff()) {
+ throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+ "Loan transaction:" + transactionId + " chargeback not allowed as loan status is written off", transactionId);
+ }
+ if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+ throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+ "Loan transaction:" + transactionId + " chargeback not allowed as loan product is interest recalculation enabled",
+ transactionId);
+ }
+
+ final List<Long> existingTransactionIds = new ArrayList<>();
+ final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+ checkClientOrGroupActive(loan);
+
+ LoanTransaction loanTransaction = this.loanTransactionRepository.findById(transactionId)
+ .orElseThrow(() -> new LoanTransactionNotFoundException(transactionId));
+
+ if (!loanTransaction.isRepayment()) {
+ throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+ "Loan transaction:" + transactionId + " chargeback not allowed as loan transaction is not repayment", transactionId);
+ }
+
+ businessEventNotifierService.notifyPreBusinessEvent(new LoanChargebackTransactionBusinessEvent(loanTransaction));
+
+ final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
+ final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME);
+ final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(LoanApiConstants.externalIdParameterName);
+
+ final Map<String, Object> changes = new LinkedHashMap<>();
+ changes.put("transactionAmount", command.stringValueOfParameterNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME));
+ changes.put("locale", command.locale());
+ changes.put("dateFormat", command.dateFormat());
+ changes.put("paymentTypeId", command.stringValueOfParameterNamed(LoanApiConstants.PAYMENT_TYPE_PARAMNAME));
+
+ final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount);
+ final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createPaymentDetail(command, changes);
+ LoanTransaction newTransaction = LoanTransaction.chargeback(loan, transactionAmountAsMoney, paymentDetail, transactionDate,
+ txnExternalId);
+
+ validateLoanTransactionAmountChargeBack(loanTransaction, newTransaction);
+
+ this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
+
+ loan.handleChargebackTransaction(newTransaction, defaultLoanLifecycleStateMachine());
+
+ loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+ // Store the Loan Transaction Relation
+ LoanTransactionRelation loanTransactionRelation = LoanTransactionRelation.instance(loanTransaction, newTransaction,
+ LoanTransactionRelationTypeEnum.CHARGEBACK);
+ this.loanTransactionRelationRepository.save(loanTransactionRelation);
+
+ this.loanTransactionRepository.saveAndFlush(newTransaction);
+
+ final String noteText = command.stringValueOfParameterNamed(LoanApiConstants.noteParamName);
+ if (StringUtils.isNotBlank(noteText)) {
+ changes.put("note", noteText);
+ Note note = Note.loanTransactionNote(loan, newTransaction, noteText);
+ this.noteRepository.save(note);
+ }
+
+ postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+ this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
+
+ businessEventNotifierService.notifyPostBusinessEvent(new LoanChargebackTransactionBusinessEvent(loanTransaction));
+
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(transactionId)
+ .withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId)
+ .with(changes).build();
+ }
+
+ private void validateLoanTransactionAmountChargeBack(LoanTransaction loanTransaction, LoanTransaction chargebackTransaction) {
+ BigDecimal actualAmount = BigDecimal.ZERO;
+ for (LoanTransactionRelation loanTransactionRelation : loanTransaction.getLoanTransactionRelations()) {
+ if (loanTransactionRelation.getRelationType().equals(LoanTransactionRelationTypeEnum.CHARGEBACK)) {
+ actualAmount = actualAmount.add(loanTransactionRelation.getToTransaction().getPrincipalPortion());
+ }
+ }
+ actualAmount = actualAmount.add(chargebackTransaction.getPrincipalPortion());
+ if (actualAmount.compareTo(loanTransaction.getPrincipalPortion()) > 0) {
+ throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+ "Loan transaction:" + loanTransaction.getId() + " chargeback not allowed as loan transaction amount is not enough",
+ loanTransaction.getId());
+ }
+ }
+
@Transactional
@Override
public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final JsonCommand command) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
index 8c0cbaecf..b737c7631 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
@@ -103,7 +103,7 @@ public final class PaymentDetail extends AbstractPersistableCustom {
}
public PaymentDetailData toData() {
- final PaymentTypeData paymentTypeData = this.paymentType.toData();
+ final PaymentTypeData paymentTypeData = null; // this.paymentType.toData();
final PaymentDetailData paymentDetailData = new PaymentDetailData(getId(), paymentTypeData, this.accountNumber, this.checkNumber,
this.routingCode, this.receiptNumber, this.bankNumber);
return paymentDetailData;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
index f967457cf..aeb359e77 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
@@ -36,9 +36,11 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
+import lombok.AllArgsConstructor;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -50,9 +52,9 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+@AllArgsConstructor
@Path("/paymenttypes")
@Component
@@ -64,34 +66,23 @@ public class PaymentTypeApiResource {
private final PaymentTypeReadPlatformService readPlatformService;
private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
- // private final String resourceNameForPermissions = "PAYMENT_TYPE";
private final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper;
- // private final Set<String> RESPONSE_DATA_PARAMETERS = new
- // HashSet<>(Arrays.asList("id", "value", "description", "isCashPayment"));
-
- @Autowired
- public PaymentTypeApiResource(PlatformSecurityContext securityContext, DefaultToApiJsonSerializer<PaymentTypeData> jsonSerializer,
- PaymentTypeReadPlatformService readPlatformService, PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper,
- ApiRequestParameterHelper apiRequestParameterHelper, PortfolioCommandSourceWritePlatformService commandWritePlatformService) {
-
- this.securityContext = securityContext;
- this.jsonSerializer = jsonSerializer;
- this.readPlatformService = readPlatformService;
- this.paymentTypeRepositoryWrapper = paymentTypeRepositoryWrapper;
- this.apiRequestParameterHelper = apiRequestParameterHelper;
- this.commandWritePlatformService = commandWritePlatformService;
- }
-
@GET
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Retrieve all Payment Types", description = "Retrieve list of payment types")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PaymentTypeApiResourceSwagger.GetPaymentTypesResponse.class)))) })
- public String getAllPaymentTypes(@Context final UriInfo uriInfo) {
+ public String getAllPaymentTypes(@Context final UriInfo uriInfo,
+ @QueryParam("onlyWithCode") @Parameter(description = "onlyWithCode") final boolean onlyWithCode) {
this.securityContext.authenticatedUser().validateHasReadPermission(PaymentTypeApiResourceConstants.resourceNameForPermissions);
- final Collection<PaymentTypeData> paymentTypes = this.readPlatformService.retrieveAllPaymentTypes();
+ Collection<PaymentTypeData> paymentTypes = null;
+ if (onlyWithCode) {
+ paymentTypes = this.readPlatformService.retrieveAllPaymentTypesWithCode();
+ } else {
+ paymentTypes = this.readPlatformService.retrieveAllPaymentTypes();
+ }
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.jsonSerializer.serialize(settings, paymentTypes, PaymentTypeApiResourceConstants.RESPONSE_DATA_PARAMETERS);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
index 8d8440983..be7b4f00f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
@@ -37,6 +37,8 @@ public final class PaymentTypeApiResourceConstants {
public static final String DESCRIPTION = "description";
public static final String ISCASHPAYMENT = "isCashPayment";
public static final String POSITION = "position";
+ public static final String CODE_NAME = "code_name";
+ public static final String IS_SYSTEM_DEFINED = "system_defined";
static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(ID, NAME, DESCRIPTION, ISCASHPAYMENT));
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
index ead8bd51d..8813ec286 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
@@ -42,6 +42,10 @@ final class PaymentTypeApiResourceSwagger {
public Boolean isCashPayment;
@Schema(example = "0")
public Integer position;
+ @Schema(example = "REPAYMENT_REFUND")
+ public String codeName;
+ @Schema(example = "false")
+ public Boolean isSystemDefined;
}
@Schema(description = "GetPaymentTypesPaymentTypeIdResponse")
@@ -59,6 +63,10 @@ final class PaymentTypeApiResourceSwagger {
public Boolean isCashPayment;
@Schema(example = "1")
public Integer position;
+ @Schema(example = "REPAYMENT_REFUND")
+ public String codeName;
+ @Schema(example = "false")
+ public Boolean isSystemDefined;
}
@Schema(description = "PostPaymentTypesRequest")
@@ -74,6 +82,10 @@ final class PaymentTypeApiResourceSwagger {
public Boolean isCashPayment;
@Schema(example = "1")
public Integer position;
+ @Schema(example = "REPAYMENT_REFUND")
+ public String codeName;
+ @Schema(example = "false")
+ public Boolean isSystemDefined;
}
@Schema(description = "PostPaymentTypesResponse")
@@ -98,6 +110,10 @@ final class PaymentTypeApiResourceSwagger {
public Boolean isCashPayment;
@Schema(example = "3")
public Integer position;
+ @Schema(example = "REPAYMENT_REFUND")
+ public String codeName;
+ @Schema(example = "false")
+ public Boolean isSystemDefined;
}
@Schema(description = "PutPaymentTypesPaymentTypeIdResponse")
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
index ee18a19ff..b626469bb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
@@ -22,8 +22,12 @@ import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+@ToString
@Getter
+@Setter
@EqualsAndHashCode
@AllArgsConstructor
public class PaymentTypeData implements Serializable {
@@ -33,16 +37,22 @@ public class PaymentTypeData implements Serializable {
private String description;
private Boolean isCashPayment;
private Long position;
+ private String codeName;
+ private Boolean isSystemDefined;
public static PaymentTypeData instance(final Long id, final String name, final String description, final Boolean isCashPayment,
final Long position) {
- return new PaymentTypeData(id, name, description, isCashPayment, position);
+ String codeName = null;
+ Boolean isSystemDefined = false;
+ return new PaymentTypeData(id, name, description, isCashPayment, position, codeName, isSystemDefined);
}
public static PaymentTypeData instance(final Long id, final String name) {
String description = null;
Boolean isCashPayment = null;
Long position = null;
- return new PaymentTypeData(id, name, description, isCashPayment, position);
+ String codeName = null;
+ Boolean isSystemDefined = false;
+ return new PaymentTypeData(id, name, description, isCashPayment, position, codeName, isSystemDefined);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
index b735af4a2..23a570242 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
@@ -43,11 +43,13 @@ public class PaymentTypeDataValidator {
private final FromJsonHelper fromApiJsonHelper;
private static final Set<String> CREATE_PAYMENT_TYPE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(PaymentTypeApiResourceConstants.NAME, PaymentTypeApiResourceConstants.DESCRIPTION,
- PaymentTypeApiResourceConstants.ISCASHPAYMENT, PaymentTypeApiResourceConstants.POSITION));
+ PaymentTypeApiResourceConstants.ISCASHPAYMENT, PaymentTypeApiResourceConstants.POSITION,
+ PaymentTypeApiResourceConstants.CODE_NAME, PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED));
private static final Set<String> UPDATE_PAYMENT_TYPE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(PaymentTypeApiResourceConstants.NAME, PaymentTypeApiResourceConstants.DESCRIPTION,
- PaymentTypeApiResourceConstants.ISCASHPAYMENT, PaymentTypeApiResourceConstants.POSITION));
+ PaymentTypeApiResourceConstants.ISCASHPAYMENT, PaymentTypeApiResourceConstants.POSITION,
+ PaymentTypeApiResourceConstants.CODE_NAME, PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED));
@Autowired
public PaymentTypeDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -93,6 +95,19 @@ public class PaymentTypeDataValidator {
.integerZeroOrGreater();
}
+ if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.CODE_NAME, element)) {
+ final String codeName = this.fromApiJsonHelper.extractStringNamed(PaymentTypeApiResourceConstants.CODE_NAME, element);
+ baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.CODE_NAME).value(codeName).ignoreIfNull()
+ .notExceedingLengthOf(100);
+ }
+
+ if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED, element)) {
+ final Boolean isSystemDefined = this.fromApiJsonHelper.extractBooleanNamed(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED,
+ element);
+ baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED).value(isSystemDefined)
+ .validateForBooleanValue();
+ }
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -142,7 +157,20 @@ public class PaymentTypeDataValidator {
.integerZeroOrGreater();
}
- throwExceptionIfValidationWarningsExist(dataValidationErrors);
+ if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.CODE_NAME, element)) {
+ final String codeName = this.fromApiJsonHelper.extractStringNamed(PaymentTypeApiResourceConstants.CODE_NAME, element);
+ baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.CODE_NAME).value(codeName).ignoreIfNull()
+ .notExceedingLengthOf(100);
+ }
+ if (this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED, element)) {
+ final Boolean isSystemDefined = this.fromApiJsonHelper.extractBooleanNamed(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED,
+ element);
+ baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED).value(isSystemDefined)
+ .validateForBooleanValue();
+ }
+
+ throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
index 1207eba64..ab1bd383b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
@@ -23,14 +23,19 @@ import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceConstants;
-import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+@Getter
+@Setter
@Entity
@Table(name = "m_payment_type")
+@AllArgsConstructor
public class PaymentType extends AbstractPersistableCustom {
@Column(name = "value")
@@ -45,18 +50,13 @@ public class PaymentType extends AbstractPersistableCustom {
@Column(name = "order_position")
private Long position;
- protected PaymentType() {}
+ @Column(name = "code_name")
+ private String codeName;
- public PaymentType(final String name, final String description, final Boolean isCashPayment, final Long position) {
- this.name = name;
- this.description = description;
- this.isCashPayment = isCashPayment;
- this.position = position;
- }
+ @Column(name = "is_system_defined")
+ private Boolean isSystemDefined;
- public static PaymentType create(String name, String description, Boolean isCashPayment, Long position) {
- return new PaymentType(name, description, isCashPayment, position);
- }
+ protected PaymentType() {}
public Map<String, Object> update(final JsonCommand command) {
@@ -89,16 +89,4 @@ public class PaymentType extends AbstractPersistableCustom {
return actualChanges;
}
- public PaymentTypeData toData() {
- return PaymentTypeData.instance(getId(), this.name, this.description, this.isCashPayment, this.position);
- }
-
- public Boolean isCashPayment() {
- return isCashPayment;
- }
-
- public String getPaymentName() {
- return name;
- }
-
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
index d4119bdf2..eaa8ed285 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
@@ -18,9 +18,14 @@
*/
package org.apache.fineract.portfolio.paymenttype.domain;
+import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface PaymentTypeRepository extends JpaRepository<PaymentType, Long>, JpaSpecificationExecutor<PaymentType> {
+ List<PaymentType> findAllByOrderByPositionAsc();
+
+ List<PaymentType> findAllByCodeNameIsNotNullOrderByPositionAsc();
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
index 6b03bdb71..8bcf81d02 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
@@ -18,18 +18,23 @@
*/
package org.apache.fineract.portfolio.paymenttype.domain;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.paymenttype.exception.PaymentTypeNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
+@RequiredArgsConstructor
public class PaymentTypeRepositoryWrapper {
private final PaymentTypeRepository repository;
- @Autowired
- public PaymentTypeRepositoryWrapper(final PaymentTypeRepository repository) {
- this.repository = repository;
+ public List<PaymentType> findAll() {
+ return this.repository.findAllByOrderByPositionAsc();
+ }
+
+ public List<PaymentType> findAllWithCodeName() {
+ return this.repository.findAllByCodeNameIsNotNullOrderByPositionAsc();
}
public PaymentType findOneWithNotFoundDetection(final Long id) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/mapper/PaymentTypeMapper.java
similarity index 53%
copy from fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
copy to fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/mapper/PaymentTypeMapper.java
index b3aead6d3..af48e1336 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/mapper/PaymentTypeMapper.java
@@ -16,15 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.portfolio.paymenttype.service;
+package org.apache.fineract.portfolio.paymenttype.mapper;
-import java.util.Collection;
+import java.util.List;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
-public interface PaymentTypeReadPlatformService {
+@Mapper(componentModel = "spring")
+public interface PaymentTypeMapper {
- Collection<PaymentTypeData> retrieveAllPaymentTypes();
+ @Mapping(target = "id", source = "source.id")
+ @Mapping(target = "name", source = "source.name")
+ @Mapping(target = "description", source = "source.description")
+ @Mapping(target = "isCashPayment", source = "source.isCashPayment")
+ @Mapping(target = "codeName", source = "source.codeName")
+ @Mapping(target = "isSystemDefined", source = "source.isSystemDefined")
+ PaymentTypeData map(PaymentType source);
- PaymentTypeData retrieveOne(Long paymentTypeId);
+ List<PaymentTypeData> map(List<PaymentType> sources);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
index b3aead6d3..072f5a903 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
@@ -25,6 +25,8 @@ public interface PaymentTypeReadPlatformService {
Collection<PaymentTypeData> retrieveAllPaymentTypes();
+ Collection<PaymentTypeData> retrieveAllPaymentTypesWithCode();
+
PaymentTypeData retrieveOne(Long paymentTypeId);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
index 65fd525b2..a7edbd5d4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
@@ -18,28 +18,24 @@
*/
package org.apache.fineract.portfolio.paymenttype.service;
-import java.sql.ResultSet;
-import java.sql.SQLException;
import java.util.Collection;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.apache.fineract.portfolio.paymenttype.mapper.PaymentTypeMapper;
import org.springframework.cache.annotation.Cacheable;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
+@RequiredArgsConstructor
public class PaymentTypeReadPlatformServiceImpl implements PaymentTypeReadPlatformService {
- private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
-
- @Autowired
- public PaymentTypeReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) {
- this.context = context;
- this.jdbcTemplate = jdbcTemplate;
- }
+ private final PaymentTypeMapper paymentTypeMapper;
+ private final PaymentTypeRepositoryWrapper paymentTypeRepository;
@Override
@Cacheable(value = "payment_types", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
@@ -47,41 +43,27 @@ public class PaymentTypeReadPlatformServiceImpl implements PaymentTypeReadPlatfo
// TODO Auto-generated method stub
this.context.authenticatedUser();
- final PaymentTypeMapper ptm = new PaymentTypeMapper();
- final String sql = "select " + ptm.schema() + "order by position";
-
- return this.jdbcTemplate.query(sql, ptm); // NOSONAR
+ List<PaymentType> paymentType = this.paymentTypeRepository.findAll();
+ return this.paymentTypeMapper.map(paymentType);
}
@Override
- public PaymentTypeData retrieveOne(Long paymentTypeId) {
+ @Cacheable(value = "paymentTypesWithCode", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
+ public Collection<PaymentTypeData> retrieveAllPaymentTypesWithCode() {
// TODO Auto-generated method stub
this.context.authenticatedUser();
- final PaymentTypeMapper ptm = new PaymentTypeMapper();
- final String sql = "select " + ptm.schema() + "where pt.id = ?";
-
- return this.jdbcTemplate.queryForObject(sql, ptm, new Object[] { paymentTypeId }); // NOSONAR
+ List<PaymentType> paymentType = this.paymentTypeRepository.findAllWithCodeName();
+ return this.paymentTypeMapper.map(paymentType);
}
- private static final class PaymentTypeMapper implements RowMapper<PaymentTypeData> {
-
- public String schema() {
- return " pt.id as id, pt.value as name, pt.description as description,pt.is_cash_payment as isCashPayment,pt.order_position as position from m_payment_type pt ";
- }
-
- @Override
- public PaymentTypeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
-
- final Long id = rs.getLong("id");
- final String name = rs.getString("name");
- final String description = rs.getString("description");
- final boolean isCashPayment = rs.getBoolean("isCashPayment");
- final Long position = rs.getLong("position");
-
- return PaymentTypeData.instance(id, name, description, isCashPayment, position);
- }
+ @Override
+ public PaymentTypeData retrieveOne(Long paymentTypeId) {
+ // TODO Auto-generated method stub
+ this.context.authenticatedUser();
+ final PaymentType paymentType = this.paymentTypeRepository.findOneWithNotFoundDetection(paymentTypeId);
+ return this.paymentTypeMapper.map(paymentType);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
index 0fcf4f26c..aa5a893c6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.paymenttype.service;
import java.util.Map;
+import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -28,28 +29,19 @@ import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeDataValidator;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
@Service
+@RequiredArgsConstructor
public class PaymentTypeWriteServiceImpl implements PaymentTypeWriteService {
private final PaymentTypeRepository repository;
private final PaymentTypeRepositoryWrapper repositoryWrapper;
private final PaymentTypeDataValidator fromApiJsonDeserializer;
- @Autowired
- public PaymentTypeWriteServiceImpl(PaymentTypeRepository repository, PaymentTypeRepositoryWrapper repositoryWrapper,
- PaymentTypeDataValidator fromApiJsonDeserializer) {
- this.repository = repository;
- this.repositoryWrapper = repositoryWrapper;
- this.fromApiJsonDeserializer = fromApiJsonDeserializer;
-
- }
-
@Override
@CacheEvict(value = "payment_types", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
public CommandProcessingResult createPaymentType(JsonCommand command) {
@@ -58,8 +50,13 @@ public class PaymentTypeWriteServiceImpl implements PaymentTypeWriteService {
String description = command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.DESCRIPTION);
Boolean isCashPayment = command.booleanObjectValueOfParameterNamed(PaymentTypeApiResourceConstants.ISCASHPAYMENT);
Long position = command.longValueOfParameterNamed(PaymentTypeApiResourceConstants.POSITION);
+ String codeName = command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.CODE_NAME);
+ Boolean isSystemDefined = command.booleanObjectValueOfParameterNamed(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED);
+ if (isSystemDefined == null) {
+ isSystemDefined = false;
+ }
- PaymentType newPaymentType = PaymentType.create(name, description, isCashPayment, position);
+ PaymentType newPaymentType = new PaymentType(name, description, isCashPayment, position, codeName, isSystemDefined);
this.repository.saveAndFlush(newPaymentType);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(newPaymentType.getId()).build();
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
index 459fac396..ae10f099a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
@@ -1322,11 +1322,11 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (charge.isEnablePaymentType() && charge.isEnableFreeWithdrawal()) { // discount transaction to
// specific paymentType
- if (paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName())) {
+ if (paymentDetail.getPaymentType().getName().equals(charge.getCharge().getPaymentType().getName())) {
resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, user, refNo);
}
} else if (charge.isEnablePaymentType()) { // normal charge-transaction to specific paymentType
- if (paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName())) {
+ if (paymentDetail.getPaymentType().getName().equals(charge.getCharge().getPaymentType().getName())) {
charge.updateWithdralFeeAmount(transactionAmount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user,
backdatedTxnsAllowedTill, refNo);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index a6fd7cb16..7a8d19d9d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -616,7 +616,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
if (paymentTypeId != null) {
final String typeName = rs.getString("paymentTypeName");
- final PaymentTypeData paymentTypeData = new PaymentTypeData(paymentTypeId, typeName, null, false, null);
+ final PaymentTypeData paymentTypeData = new PaymentTypeData(paymentTypeId, typeName, null, false, null, null,
+ false);
paymentDetailData = new PaymentDetailData(id, paymentTypeData, null, null, null, null, null);
}
}
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 6ec9cf739..ba4e3bfc4 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -71,4 +71,5 @@
<include file="parts/0049_add_send_asynchronous_events_job.xml" relativeToChangelogFile="true"/>
<include file="parts/0050_add_reverse_flag_disbursement_details.xml" relativeToChangelogFile="true"/>
<include file="parts/0051_external_event_table_category_info.xml" relativeToChangelogFile="true"/>
+ <include file="parts/0052_loan_transaction_chargeback.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0052_loan_transaction_chargeback.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0052_loan_transaction_chargeback.xml
new file mode 100644
index 000000000..638f126fe
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0052_loan_transaction_chargeback.xml
@@ -0,0 +1,134 @@
+<?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.3.xsd">
+ <changeSet author="fineract" id="1" context="mysql">
+ <createTable tableName="m_loan_transaction_relation">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="from_loan_transaction_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="to_loan_transaction_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="relation_type_enum" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="created_by" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="created_on_utc" type="DATETIME">
+ <constraints nullable="false" />
+ </column>
+ <column name="version" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="last_modified_by" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="last_modified_on_utc" type="DATETIME">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+ </changeSet>
+ <changeSet author="fineract" id="1" context="postgresql">
+ <createTable tableName="m_loan_transaction_relation">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="from_loan_transaction_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="to_loan_transaction_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="relation_type_enum" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="created_by" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
+ <constraints nullable="false" />
+ </column>
+ <column name="version" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="last_modified_by" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+ </changeSet>
+ <changeSet author="fineract" id="2">
+ <addForeignKeyConstraint baseColumnNames="from_loan_transaction_id" baseTableName="m_loan_transaction_relation"
+ constraintName="FK_m_loan_transaction_from_relation" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_loan_transaction" validate="true"/>
+ <addForeignKeyConstraint baseColumnNames="to_loan_transaction_id" baseTableName="m_loan_transaction_relation"
+ constraintName="FK_m_loan_transaction_to_relation" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_loan_transaction" validate="true"/>
+ </changeSet>
+ <changeSet author="fineract" id="3">
+ <insert tableName="m_permission">
+ <column name="grouping" value="transaction_loan"/>
+ <column name="code" value="CHARGEBACK_LOAN"/>
+ <column name="entity_name" value="LOAN"/>
+ <column name="action_name" value="CHARGEBACK"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ </changeSet>
+ <changeSet author="fineract" id="4">
+ <addColumn tableName="m_payment_type">
+ <column name="code_name" type="VARCHAR(100)"/>
+ </addColumn>
+ <addColumn tableName="m_payment_type">
+ <column name="is_system_defined" type="boolean" defaultValueComputed="false">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+ <changeSet author="fineract" id="5">
+ <insert tableName="m_payment_type">
+ <column name="value" value="Repayment Adjustment Chargeback"/>
+ <column name="description" value="Repayment Adjustment Chargeback"/>
+ <column name="is_cash_payment" valueBoolean="false"/>
+ <column name="order_position" valueNumeric="1"/>
+ <column name="code_name" value="REPAYMENT_ADJUSTMENT_CHARGEBACK"/>
+ <column name="is_system_defined" valueNumeric="true"/>
+ </insert>
+ <insert tableName="m_payment_type">
+ <column name="value" value="Repayment Adjustment Refund"/>
+ <column name="description" value="Repayment Adjustment Refund"/>
+ <column name="is_cash_payment" valueBoolean="false"/>
+ <column name="order_position" valueNumeric="1"/>
+ <column name="code_name" value="REPAYMENT_ADJUSTMENT_REFUND"/>
+ <column name="is_system_defined" valueBoolean="true"/>
+ </insert>
+ </changeSet>
+</databaseChangeLog>
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java
new file mode 100644
index 000000000..cd07d376d
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java
@@ -0,0 +1,138 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.google.gson.Gson;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+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.GetLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.GetPaymentTypesResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@Slf4j
+public class LoanTransactionChargebackTest {
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+
+ @BeforeEach
+ public void setup() {
+ Utils.initializeRESTAssured();
+ this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+ }
+
+ @Test
+ public void applyLoanTransactionChargeback() {
+ final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+ // Client and Loan account creation
+ final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper, null);
+ assertNotNull(getLoanProductsProductResponse);
+ log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
+
+ final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+ // Older date to have more than one overdue installment
+ final LocalDate transactionDate = todaysDate.minusDays(45);
+ String operationDate = Utils.dateFormatter.format(transactionDate);
+
+ final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
+ getLoanProductsProductResponse.getId().toString(), operationDate, "1000");
+
+ GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ assertNotNull(getLoansLoanIdResponse);
+
+ loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+
+ operationDate = Utils.dateFormatter.format(todaysDate);
+ Float amount = Float.valueOf("1100.0");
+ PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount,
+ loanId);
+ assertNotNull(loanIdTransactionsResponse);
+ final Integer transactionId = loanIdTransactionsResponse.getResourceId();
+
+ List<GetPaymentTypesResponse> paymentTypeList = PaymentTypeHelper.getSystemPaymentType(requestSpec, responseSpec);
+ assertTrue(!paymentTypeList.isEmpty());
+
+ amount = Float.valueOf("800.0");
+ final String payload = createChargebackPayload(amount.toString(), paymentTypeList.get(0).getId());
+ PostLoansLoanIdTransactionsTransactionIdRequest postLoansTransactionCommandRequest = loanTransactionHelper
+ .applyLoanTransactionCommand(loanId, transactionId, "chargeback", payload);
+ assertNotNull(postLoansTransactionCommandRequest);
+
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+ assertNotNull(getLoansLoanIdResponse);
+ }
+
+ private String createChargebackPayload(final String transactionAmount, final Integer paymentTypeId) {
+ final HashMap<String, Object> map = new HashMap<>();
+ map.put("transactionAmount", transactionAmount);
+ map.put("paymentTypeId", paymentTypeId);
+ map.put("locale", CommonConstants.LOCALE);
+ final String chargebackPayload = new Gson().toJson(map);
+ log.info("{}", chargebackPayload);
+ return chargebackPayload;
+ }
+
+ private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
+ final Integer delinquencyBucketId) {
+ final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);
+ final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+ return loanTransactionHelper.getLoanProduct(loanProductId);
+ }
+
+ private Integer createLoanAccount(final LoanTransactionHelper loanTransactionHelper, final String clientId, final String loanProductId,
+ final String operationDate, final String principalAmount) {
+ final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency("12")
+ .withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
+ .withRepaymentFrequencyTypeAsMonths() //
+ .withInterestRatePerPeriod("2") //
+ .withExpectedDisbursementDate(operationDate) //
+ .withInterestTypeAsDecliningBalance() //
+ .withSubmittedOnDate(operationDate) //
+ .build(clientId, loanProductId, null);
+ final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
+ loanTransactionHelper.approveLoan(operationDate, principalAmount, loanId, null);
+ loanTransactionHelper.disburseLoanWithNetDisbursalAmount(operationDate, loanId, principalAmount);
+ return loanId;
+ }
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
index 9a54fa5e2..0f2c396dc 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
@@ -24,7 +24,10 @@ import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
import java.util.HashMap;
+import org.apache.fineract.client.models.GetPaymentTypesResponse;
@SuppressWarnings({ "rawtypes", "unchecked" })
public final class PaymentTypeHelper {
@@ -33,8 +36,16 @@ public final class PaymentTypeHelper {
}
- private static final String CREATE_PAYMENTTYPE_URL = "/fineract-provider/api/v1/paymenttypes?" + Utils.TENANT_IDENTIFIER;
private static final String PAYMENTTYPE_URL = "/fineract-provider/api/v1/paymenttypes";
+ private static final String CREATE_PAYMENTTYPE_URL = PAYMENTTYPE_URL + "?" + Utils.TENANT_IDENTIFIER;
+
+ public static ArrayList<GetPaymentTypesResponse> getSystemPaymentType(final RequestSpecification requestSpec,
+ final ResponseSpecification responseSpec) {
+ String response = Utils.performServerGet(requestSpec, responseSpec,
+ PAYMENTTYPE_URL + "?onlyWithCode=true&" + Utils.TENANT_IDENTIFIER);
+ Type paymentTypeList = new TypeToken<ArrayList<GetPaymentTypesResponse>>() {}.getType();
+ return new Gson().fromJson(response, paymentTypeList);
+ }
public static Integer createPaymentType(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String name, final String description, final Boolean isCashPayment, final Integer position) {
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 96c51a0f5..aeb79b8d9 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -47,6 +47,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.CommonConstants;
@@ -239,6 +240,14 @@ public class LoanTransactionHelper {
return Utils.performServerGet(requestSpec, responseSpec, GET_REPAYMENTS_URL, "loanRepaymentScheduleInstallments");
}
+ public PostLoansLoanIdTransactionsTransactionIdRequest applyLoanTransactionCommand(final Integer loanId, final Integer transactionId,
+ final String command, final String payload) {
+ final String LOAN_TRANSACTION_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/" + transactionId + "?command="
+ + command + "&" + Utils.TENANT_IDENTIFIER;
+ final String response = Utils.performServerPost(requestSpec, responseSpec, LOAN_TRANSACTION_URL, payload, null);
+ return GSON.fromJson(response, PostLoansLoanIdTransactionsTransactionIdRequest.class);
+ }
+
public HashMap approveLoan(final String approvalDate, final Integer loanID) {
String loanApprovalCommand = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID);
String loanApprovalRequest = getApproveLoanAsJSON(approvalDate);