Credit Balance Refund loan transaction

This commit is contained in:
John Woodlock
2022-04-19 10:04:16 +02:00
committed by Arnold Gálovics
parent 3d3e8c4083
commit d1168833cc
24 changed files with 671 additions and 21 deletions
@@ -80,6 +80,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
}
/** Logic for Credit Balance Refunds **/
else if (loanTransactionDTO.getTransactionType().isCreditBalanceRefund()) {
createJournalEntriesForCreditBalanceRefund(loanDTO, loanTransactionDTO, office);
}
/** Handle Write Offs, waivers and their reversals **/
else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest()
|| loanTransactionDTO.getTransactionType().isWaiveCharges())) {
@@ -397,6 +402,25 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
}
private void createJournalEntriesForCreditBalanceRefund(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) {
// loan properties
final Long loanProductId = loanDTO.getLoanProductId();
final Long loanId = loanDTO.getLoanId();
final String currencyCode = loanDTO.getCurrencyCode();
// transaction properties
final String transactionId = loanTransactionDTO.getTransactionId();
final Date transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal refundAmount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
this.helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), AccrualAccountsForLoan.OVERPAYMENT.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal);
}
private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
// TODO Auto-generated method stub
// loan properties
@@ -878,6 +878,15 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder creditBalanceRefund(final Long loanId) {
this.actionName = "CREDITBALANCEREFUND";
this.entityName = "LOAN";
this.entityId = null;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/transactions?command=creditBalanceRefund";
return this;
}
public CommandWrapperBuilder undoWaiveChargeTransaction(final Long loanId, final Long transactionId) {
this.actionName = "UNDO";
this.entityName = "WAIVECHARGE";
@@ -55,6 +55,7 @@ public class BusinessEventNotificationConstants {
LOAN_INTEREST_RECALCULATION("loan_interest_recalculation"), //
LOAN_REFUND("loan_refund"), //
LOAN_FORECLOSURE("loan_foreclosure"), //
LOAN_CREDIT_BALANCE_REFUND("loan_credit_balance_refund"), //
LOAN_CREATE("loan_create"), //
LOAN_PRODUCT_CREATE("loan_product_create"), //
SAVINGS_ACTIVATE("savings_activated"), //
@@ -164,6 +164,8 @@ public class LoanTransactionsApiResource {
DateUtils.getDateTimeZoneOfTenant());
}
transactionData = this.loanReadPlatformService.retrieveLoanForeclosureTemplate(loanId, transactionDate);
} else if (is(commandParam, "creditBalanceRefund")) {
transactionData = this.loanReadPlatformService.retrieveCreditBalanceRefundTemplate(loanId);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
@@ -239,6 +241,9 @@ public class LoanTransactionsApiResource {
} else if (is(commandParam, "foreclosure")) {
final CommandWrapper commandRequest = builder.loanForeclosure(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "creditBalanceRefund")) {
final CommandWrapper commandRequest = builder.creditBalanceRefund(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
if (result == null) {
@@ -44,6 +44,7 @@ public class LoanTransactionEnumData {
private final boolean chargePayment;
private final boolean refund;
private final boolean refundForActiveLoans;
private final boolean creditBalanceRefund;
public LoanTransactionEnumData(final Long id, final String code, final String value) {
this.id = id;
@@ -65,6 +66,7 @@ public class LoanTransactionEnumData {
this.refund = Long.valueOf(16).equals(this.id);
this.chargePayment = Long.valueOf(17).equals(this.id);
this.refundForActiveLoans = Long.valueOf(18).equals(this.id);
this.creditBalanceRefund = Long.valueOf(20).equals(this.id);
}
public Long id() {
@@ -152,4 +154,8 @@ public class LoanTransactionEnumData {
return this.refundForActiveLoans;
}
public boolean isCreditBalanceRefund() {
return this.creditBalanceRefund;
}
}
@@ -106,6 +106,8 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
break;
case WRITE_OFF_OUTSTANDING_UNDO:
break;
case LOAN_CREDIT_BALANCE_REFUND:
break;
default:
break;
}
@@ -3615,10 +3615,11 @@ public class Loan extends AbstractPersistableCustom {
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
transactionForAdjustment.getTransactionDate());
if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()) {
final String errorMessage = "Only transactions of type repayment or waiver can be adjusted.";
throw new InvalidLoanTransactionTypeException("transaction", "adjustment.is.only.allowed.to.repayment.or.waiver.transaction",
errorMessage);
if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()
&& transactionForAdjustment.isNotCreditBalanceRefund()) {
final String errorMessage = "Only transactions of type repayment, waiver or credit balance refund can be adjusted.";
throw new InvalidLoanTransactionTypeException("transaction",
"adjustment.is.only.allowed.to.repayment.or.waiver.or.creditbalancerefund.transactions", errorMessage);
}
transactionForAdjustment.reverse();
@@ -3698,7 +3699,8 @@ public class Loan extends AbstractPersistableCustom {
}
for (final LoanTransaction loanTransaction : this.loanTransactions) {
if ((loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) && !loanTransaction.isReversed()) {
if ((loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan() || loanTransaction.isCreditBalanceRefund())
&& !loanTransaction.isReversed()) {
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
}
}
@@ -5157,6 +5159,14 @@ public class Loan extends AbstractPersistableCustom {
dataValidationErrors.add(error);
}
break;
case LOAN_CREDIT_BALANCE_REFUND:
if (!status().isOverpaid()) {
final String defaultUserMessage = "Loan Credit Balance Refund is not allowed. Loan Account is not Overpaid.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.credit.balance.refund.account.is.not.overpaid", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
default:
break;
}
@@ -6062,6 +6072,40 @@ public class Loan extends AbstractPersistableCustom {
return this.guaranteeAmountDerived == null ? BigDecimal.ZERO : this.guaranteeAmountDerived;
}
public void creditBalanceRefund(LoanTransaction newCreditBalanceRefundTransaction,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, List<Long> existingTransactionIds,
List<Long> existingReversedTransactionIds) {
validateAccountStatus(LoanEvent.LOAN_CREDIT_BALANCE_REFUND);
validateRefundDateIsAfterLastRepayment(newCreditBalanceRefundTransaction.getTransactionDate());
if (!newCreditBalanceRefundTransaction.isGreaterThanZeroAndLessThanOrEqualTo(this.totalOverpaid)) {
final String errorMessage = "Transaction Amount ("
+ newCreditBalanceRefundTransaction.getAmount(getCurrency()).getAmount().toString()
+ ") must be > zero and <= Overpaid amount (" + this.totalOverpaid.toString() + ").";
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final ApiParameterError error = ApiParameterError.parameterError(
"error.msg.transactionAmount.invalid.must.be.>zero.and<=overpaidamount", errorMessage, "transactionAmount",
newCreditBalanceRefundTransaction.getAmount(getCurrency()));
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
this.loanTransactions.add(newCreditBalanceRefundTransaction);
updateLoanSummaryDerivedFields();
if (this.totalOverpaid == null || BigDecimal.ZERO.compareTo(this.totalOverpaid) == 0) {
this.loanStatus = LoanStatus.CLOSED_OBLIGATIONS_MET.getValue();
}
}
public ChangedTransactionDetail makeRefundForActiveLoan(final LoanTransaction loanTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
@@ -6179,7 +6223,8 @@ public class Loan extends AbstractPersistableCustom {
LocalDate lastTransactionDate = null;
for (final LoanTransaction transaction : this.loanTransactions) {
if ((transaction.isRepayment() || transaction.isRefundForActiveLoan()) && transaction.isNonZero()) {
if ((transaction.isRepayment() || transaction.isRefundForActiveLoan() || transaction.isCreditBalanceRefund())
&& transaction.isNonZero() && transaction.isNotReversed()) {
lastTransactionDate = transaction.getTransactionDate();
}
}
@@ -6841,4 +6886,5 @@ public class Loan extends AbstractPersistableCustom {
public void adjustNetDisbursalAmount(BigDecimal adjustedAmount) {
this.netDisbursalAmount = adjustedAmount.subtract(this.deriveSumTotalOfChargesDueAtDisbursement());
}
}
@@ -78,4 +78,7 @@ public interface LoanAccountDomainService {
void disableStandingInstructionsLinkedToClosedLoan(Loan loan);
void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened);
CommandProcessingResultBuilder creditBalanceRefund(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
String noteText, String externalId);
}
@@ -623,6 +623,41 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return user;
}
@Override
public CommandProcessingResultBuilder creditBalanceRefund(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
String noteText, String externalId) {
final Loan loan = this.loanAccountAssembler.assembleFrom(loanId);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BusinessEvents.LOAN_CREDIT_BALANCE_REFUND,
constructEntityMap(BusinessEntity.LOAN, loan));
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
AppUser currentUser = getAppUserIfPresent();
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
final LoanTransaction newCreditBalanceRefundTransaction = LoanTransaction.creditBalanceRefund(loan, loan.getOffice(), refundAmount,
transactionDate, externalId, DateUtils.getLocalDateTimeOfTenant(), currentUser);
loan.creditBalanceRefund(newCreditBalanceRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds,
existingReversedTransactionIds);
this.loanTransactionRepository.saveAndFlush(newCreditBalanceRefundTransaction);
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, newCreditBalanceRefundTransaction, noteText);
this.noteRepository.save(note);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.LOAN_CREDIT_BALANCE_REFUND,
constructEntityMap(BusinessEntity.LOAN_TRANSACTION, newCreditBalanceRefundTransaction));
return new CommandProcessingResultBuilder().withEntityId(newCreditBalanceRefundTransaction.getId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId());
}
@Override
public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) {
@@ -794,4 +829,5 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
}
}
}
@@ -43,5 +43,6 @@ public enum LoanEvent {
LOAN_CLOSED, //
LOAN_EDIT_MULTI_DISBURSE_DATE, //
LOAN_REFUND, //
LOAN_FORECLOSURE;
LOAN_FORECLOSURE, //
LOAN_CREDIT_BALANCE_REFUND;
}
@@ -297,6 +297,13 @@ public class LoanTransaction extends AbstractPersistableCustom {
return applyCharge;
}
public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate,
final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
final PaymentDetail paymentDetail = null;
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND, paymentDetail, amount.getAmount(), paymentDate,
externalId, createdDate, appUser);
}
public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(), paymentDate,
@@ -573,6 +580,10 @@ public class LoanTransaction extends AbstractPersistableCustom {
return !isInterestWaiver() && !isChargesWaiver();
}
public boolean isNotCreditBalanceRefund() {
return !isCreditBalanceRefund();
}
public boolean isChargePayment() {
return getTypeOf().isChargePayment() && isNotReversed();
}
@@ -616,6 +627,10 @@ public class LoanTransaction extends AbstractPersistableCustom {
return getAmount(currency).isGreaterThanZero();
}
public boolean isGreaterThanZeroAndLessThanOrEqualTo(BigDecimal totalOverpaid) {
return isNonZero() && this.amount.compareTo(totalOverpaid) <= 0;
}
public boolean isNotZero(final MonetaryCurrency currency) {
return !getAmount(currency).isZero();
}
@@ -700,6 +715,10 @@ public class LoanTransaction extends AbstractPersistableCustom {
return LoanTransactionType.REFUND.equals(getTypeOf()) && isNotReversed();
}
public boolean isCreditBalanceRefund() {
return LoanTransactionType.CREDIT_BALANCE_REFUND.equals(getTypeOf()) && isNotReversed();
}
public void updateExternalId(final String externalId) {
this.externalId = externalId;
}
@@ -51,7 +51,8 @@ public enum LoanTransactionType {
REFUND(16, "loanTransactionType.refund"), //
CHARGE_PAYMENT(17, "loanTransactionType.chargePayment"), //
REFUND_FOR_ACTIVE_LOAN(18, "loanTransactionType.refund"), //
INCOME_POSTING(19, "loanTransactionType.incomePosting");
INCOME_POSTING(19, "loanTransactionType.incomePosting"), //
CREDIT_BALANCE_REFUND(20, "loanTransactionType.creditBalanceRefund");
private final Integer value;
private final String code;
@@ -131,6 +132,9 @@ public enum LoanTransactionType {
case 19:
loanTransactionType = LoanTransactionType.INCOME_POSTING;
break;
case 20:
loanTransactionType = LoanTransactionType.CREDIT_BALANCE_REFUND;
break;
default:
loanTransactionType = LoanTransactionType.INVALID;
break;
@@ -0,0 +1,72 @@
/**
* 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 lombok.extern.slf4j.Slf4j;
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.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@RequiredArgsConstructor
@Service
@CommandType(entity = "LOAN", action = "CREDITBALANCEREFUND")
public class CreditBalanceRefundCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
try {
return this.writePlatformService.creditBalanceRefund(command.getLoanId(), command);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.creditBalanceRefund", "Credit Balance Refund");
return CommandProcessingResult.empty();
}
}
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve, final String msgType,
final String msgDescription) {
if (realCause.getMessage().contains("external_id")) {
final String externalId = command.stringValueOfParameterNamed("externalId");
throw new PlatformDataIntegrityException("error.msg." + msgType + ".duplicate.externalId",
msgDescription + " with externalId `" + externalId + "` already exists", "externalId", externalId);
}
logAsErrorUnexpectedDataIntegrityException(dve);
throw new PlatformDataIntegrityException("error.msg.loan.charge.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) {
log.error("Error occured.", dve);
}
}
@@ -529,6 +529,9 @@ public final class LoanEventApiJsonValidator {
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100);
validatePaymentDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -128,6 +128,8 @@ public interface LoanReadPlatformService {
LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
LoanTransactionData retrieveCreditBalanceRefundTemplate(Long loanId);
Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(LoanAccountData loan);
Collection<Long> retrieveLoanIdsWithPendingIncomePostingTransactions();
@@ -2118,24 +2118,38 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public LoanTransactionData retrieveRefundByCashTemplate(Long loanId) {
// TODO Auto-generated method stub
this.context.authenticatedUser();
// TODO - KW - OPTIMIZE - write simple sql query to fetch back date of
// possible next transaction date.
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
final MonetaryCurrency currency = loan.getCurrency();
return retrieveRefundTemplate(loanId, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentOptions, loan.getCurrency(),
retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), loan.getNetDisbursalAmount());
}
@Override
public LoanTransactionData retrieveCreditBalanceRefundTemplate(Long loanId) {
this.context.authenticatedUser();
final Collection<PaymentTypeData> paymentOptions = null;
final BigDecimal netDisbursal = null;
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
return retrieveRefundTemplate(loanId, LoanTransactionType.CREDIT_BALANCE_REFUND, paymentOptions, loan.getCurrency(),
loan.getTotalOverpaid(), netDisbursal);
}
private LoanTransactionData retrieveRefundTemplate(Long loanId, LoanTransactionType loanTransactionType,
Collection<PaymentTypeData> paymentOptions, MonetaryCurrency currency, BigDecimal transactionAmount, BigDecimal netDisbursal) {
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CurrencyData currencyData = applicationCurrency.toData();
final LocalDate earliestUnpaidInstallmentDate = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
final LocalDate currentDate = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN);
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, loan.getNetDisbursalAmount(), null, null, null, null,
null, paymentOptions, null, null, null, null, false);
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(loanTransactionType);
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, currentDate, transactionAmount, null,
netDisbursal, null, null, null, null, null, paymentOptions, null, null, null, null, false);
}
@Override
@@ -116,4 +116,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult makeGLIMLoanRepayment(Long loanId, JsonCommand command);
CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand command);
}
@@ -3213,10 +3213,40 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return installments;
}
@Override
public CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand command) {
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final String noteText = command.stringValueOfParameterNamedAllowingNull("note");
final String externalId = command.stringValueOfParameterNamedAllowingNull("externalId");
final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
}
if (StringUtils.isNotBlank(externalId)) {
changes.put("externalId", externalId);
}
final CommandProcessingResultBuilder commandProcessingResultBuilder = this.loanAccountDomainService.creditBalanceRefund(loanId,
transactionDate, transactionAmount, noteText, externalId);
return commandProcessingResultBuilder //
.withCommandId(command.commandId()).with(changes) //
.build();
}
@Override
@Transactional
public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) {
// TODO Auto-generated method stub
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
@@ -3400,4 +3430,5 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
}
@@ -411,6 +411,10 @@ public final class LoanEnumerations {
optionData = new LoanTransactionEnumData(LoanTransactionType.INCOME_POSTING.getValue().longValue(),
LoanTransactionType.INCOME_POSTING.getCode(), "Income Posting");
break;
case CREDIT_BALANCE_REFUND:
optionData = new LoanTransactionEnumData(LoanTransactionType.CREDIT_BALANCE_REFUND.getValue().longValue(),
LoanTransactionType.CREDIT_BALANCE_REFUND.getCode(), "Credit Balance Refund");
break;
}
return optionData;
}
@@ -30,4 +30,5 @@
<include file="parts/0008_loan_charge_add_external_id.xml" relativeToChangelogFile="true"/>
<include file="parts/0009_hold_reason_savings_account.xml" relativeToChangelogFile="true"/>
<include file="parts/0010_lien_allowed_on_savings_account_products.xml" relativeToChangelogFile="true"/>
<include file="parts/0011_add_credit_balance_refund_permission.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<insert tableName="m_permission">
<column name="grouping" value="transaction_loan" />
<column name="code" value="CREDITBALANCEREFUND_LOAN" />
<column name="entity_name" value="LOAN" />
<column name="action_name" value="CREDITBALANCEREFUND" />
<column name="can_maker_checker" valueBoolean="false" />
</insert>
</changeSet>
</databaseChangeLog>