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 <[email protected]>
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);