Chargeback Loan Transaction
This commit is contained in:
committed by
Aleksandar Vidakovic
parent
5bd804ada8
commit
40b6eb50f6
@@ -42,6 +42,22 @@
|
||||
"null",
|
||||
"long"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "codeName",
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "isSystemDefined",
|
||||
"type": [
|
||||
"null",
|
||||
"boolean"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+9
@@ -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";
|
||||
|
||||
+40
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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:
|
||||
|
||||
+17
@@ -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
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+5
@@ -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";
|
||||
}
|
||||
|
||||
+9
-2
@@ -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);
|
||||
|
||||
|
||||
+6
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -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.data;
|
||||
|
||||
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;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class LoanTransactionRelationData implements Serializable {
|
||||
|
||||
private Long fromLoanTransaction;
|
||||
private Long toLoanTransaction;
|
||||
private LoanTransactionRelationTypeEnum relationType;
|
||||
private BigDecimal amount;
|
||||
private String paymentType;
|
||||
|
||||
}
|
||||
+12
-6
@@ -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) {
|
||||
|
||||
+2
-1
@@ -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;
|
||||
}
|
||||
|
||||
+23
-1
@@ -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.
|
||||
}
|
||||
|
||||
+65
@@ -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);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface LoanTransactionRelationRepository
|
||||
extends JpaRepository<LoanTransactionRelation, Long>, JpaSpecificationExecutor<LoanTransactionRelation> {
|
||||
|
||||
List<LoanTransactionRelation> findByFromTransactionAndRelationType(LoanTransaction loanTransaction,
|
||||
LoanTransactionRelationTypeEnum relationType);
|
||||
|
||||
}
|
||||
+54
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+43
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -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);
|
||||
|
||||
}
|
||||
+42
-2
@@ -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) {
|
||||
|
||||
+4
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+43
-57
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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);
|
||||
|
||||
+103
-1
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+11
-20
@@ -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);
|
||||
}
|
||||
|
||||
+2
@@ -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));
|
||||
}
|
||||
|
||||
+16
@@ -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")
|
||||
|
||||
+12
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+31
-3
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-24
@@ -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,19 +50,14 @@ public class PaymentType extends AbstractPersistableCustom {
|
||||
@Column(name = "order_position")
|
||||
private Long position;
|
||||
|
||||
@Column(name = "code_name")
|
||||
private String codeName;
|
||||
|
||||
@Column(name = "is_system_defined")
|
||||
private Boolean isSystemDefined;
|
||||
|
||||
protected PaymentType() {}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static PaymentType create(String name, String description, Boolean isCashPayment, Long position) {
|
||||
return new PaymentType(name, description, isCashPayment, position);
|
||||
}
|
||||
|
||||
public Map<String, Object> update(final JsonCommand command) {
|
||||
|
||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(3);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
+9
-4
@@ -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) {
|
||||
|
||||
+40
@@ -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.portfolio.paymenttype.mapper;
|
||||
|
||||
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;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface PaymentTypeMapper {
|
||||
|
||||
@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);
|
||||
|
||||
List<PaymentTypeData> map(List<PaymentType> sources);
|
||||
|
||||
}
|
||||
+2
@@ -25,6 +25,8 @@ public interface PaymentTypeReadPlatformService {
|
||||
|
||||
Collection<PaymentTypeData> retrieveAllPaymentTypes();
|
||||
|
||||
Collection<PaymentTypeData> retrieveAllPaymentTypesWithCode();
|
||||
|
||||
PaymentTypeData retrieveOne(Long paymentTypeId);
|
||||
|
||||
}
|
||||
|
||||
+21
-39
@@ -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,10 +43,18 @@ 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";
|
||||
List<PaymentType> paymentType = this.paymentTypeRepository.findAll();
|
||||
return this.paymentTypeMapper.map(paymentType);
|
||||
}
|
||||
|
||||
return this.jdbcTemplate.query(sql, ptm); // NOSONAR
|
||||
@Override
|
||||
@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();
|
||||
|
||||
List<PaymentType> paymentType = this.paymentTypeRepository.findAllWithCodeName();
|
||||
return this.paymentTypeMapper.map(paymentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,30 +62,8 @@ public class PaymentTypeReadPlatformServiceImpl implements PaymentTypeReadPlatfo
|
||||
// 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
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
final PaymentType paymentType = this.paymentTypeRepository.findOneWithNotFoundDetection(paymentTypeId);
|
||||
return this.paymentTypeMapper.map(paymentType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-11
@@ -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();
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+134
@@ -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>
|
||||
+138
@@ -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;
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -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) {
|
||||
|
||||
+9
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user