Credit Balance Refund loan transaction
This commit is contained in:
committed by
Arnold Gálovics
parent
3d3e8c4083
commit
d1168833cc
+24
@@ -80,6 +80,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
|||||||
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
|
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 **/
|
/** Handle Write Offs, waivers and their reversals **/
|
||||||
else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest()
|
else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest()
|
||||||
|| loanTransactionDTO.getTransactionType().isWaiveCharges())) {
|
|| 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) {
|
private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
// loan properties
|
// loan properties
|
||||||
|
|||||||
+9
@@ -878,6 +878,15 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
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) {
|
public CommandWrapperBuilder undoWaiveChargeTransaction(final Long loanId, final Long transactionId) {
|
||||||
this.actionName = "UNDO";
|
this.actionName = "UNDO";
|
||||||
this.entityName = "WAIVECHARGE";
|
this.entityName = "WAIVECHARGE";
|
||||||
|
|||||||
+1
@@ -55,6 +55,7 @@ public class BusinessEventNotificationConstants {
|
|||||||
LOAN_INTEREST_RECALCULATION("loan_interest_recalculation"), //
|
LOAN_INTEREST_RECALCULATION("loan_interest_recalculation"), //
|
||||||
LOAN_REFUND("loan_refund"), //
|
LOAN_REFUND("loan_refund"), //
|
||||||
LOAN_FORECLOSURE("loan_foreclosure"), //
|
LOAN_FORECLOSURE("loan_foreclosure"), //
|
||||||
|
LOAN_CREDIT_BALANCE_REFUND("loan_credit_balance_refund"), //
|
||||||
LOAN_CREATE("loan_create"), //
|
LOAN_CREATE("loan_create"), //
|
||||||
LOAN_PRODUCT_CREATE("loan_product_create"), //
|
LOAN_PRODUCT_CREATE("loan_product_create"), //
|
||||||
SAVINGS_ACTIVATE("savings_activated"), //
|
SAVINGS_ACTIVATE("savings_activated"), //
|
||||||
|
|||||||
+5
@@ -164,6 +164,8 @@ public class LoanTransactionsApiResource {
|
|||||||
DateUtils.getDateTimeZoneOfTenant());
|
DateUtils.getDateTimeZoneOfTenant());
|
||||||
}
|
}
|
||||||
transactionData = this.loanReadPlatformService.retrieveLoanForeclosureTemplate(loanId, transactionDate);
|
transactionData = this.loanReadPlatformService.retrieveLoanForeclosureTemplate(loanId, transactionDate);
|
||||||
|
} else if (is(commandParam, "creditBalanceRefund")) {
|
||||||
|
transactionData = this.loanReadPlatformService.retrieveCreditBalanceRefundTemplate(loanId);
|
||||||
} else {
|
} else {
|
||||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||||
}
|
}
|
||||||
@@ -239,6 +241,9 @@ public class LoanTransactionsApiResource {
|
|||||||
} else if (is(commandParam, "foreclosure")) {
|
} else if (is(commandParam, "foreclosure")) {
|
||||||
final CommandWrapper commandRequest = builder.loanForeclosure(loanId).build();
|
final CommandWrapper commandRequest = builder.loanForeclosure(loanId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
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) {
|
if (result == null) {
|
||||||
|
|||||||
+6
@@ -44,6 +44,7 @@ public class LoanTransactionEnumData {
|
|||||||
private final boolean chargePayment;
|
private final boolean chargePayment;
|
||||||
private final boolean refund;
|
private final boolean refund;
|
||||||
private final boolean refundForActiveLoans;
|
private final boolean refundForActiveLoans;
|
||||||
|
private final boolean creditBalanceRefund;
|
||||||
|
|
||||||
public LoanTransactionEnumData(final Long id, final String code, final String value) {
|
public LoanTransactionEnumData(final Long id, final String code, final String value) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -65,6 +66,7 @@ public class LoanTransactionEnumData {
|
|||||||
this.refund = Long.valueOf(16).equals(this.id);
|
this.refund = Long.valueOf(16).equals(this.id);
|
||||||
this.chargePayment = Long.valueOf(17).equals(this.id);
|
this.chargePayment = Long.valueOf(17).equals(this.id);
|
||||||
this.refundForActiveLoans = Long.valueOf(18).equals(this.id);
|
this.refundForActiveLoans = Long.valueOf(18).equals(this.id);
|
||||||
|
this.creditBalanceRefund = Long.valueOf(20).equals(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long id() {
|
public Long id() {
|
||||||
@@ -152,4 +154,8 @@ public class LoanTransactionEnumData {
|
|||||||
return this.refundForActiveLoans;
|
return this.refundForActiveLoans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCreditBalanceRefund() {
|
||||||
|
return this.creditBalanceRefund;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -106,6 +106,8 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
break;
|
break;
|
||||||
case WRITE_OFF_OUTSTANDING_UNDO:
|
case WRITE_OFF_OUTSTANDING_UNDO:
|
||||||
break;
|
break;
|
||||||
|
case LOAN_CREDIT_BALANCE_REFUND:
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-6
@@ -3615,10 +3615,11 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
|
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
|
||||||
transactionForAdjustment.getTransactionDate());
|
transactionForAdjustment.getTransactionDate());
|
||||||
|
|
||||||
if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()) {
|
if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()
|
||||||
final String errorMessage = "Only transactions of type repayment or waiver can be adjusted.";
|
&& transactionForAdjustment.isNotCreditBalanceRefund()) {
|
||||||
throw new InvalidLoanTransactionTypeException("transaction", "adjustment.is.only.allowed.to.repayment.or.waiver.transaction",
|
final String errorMessage = "Only transactions of type repayment, waiver or credit balance refund can be adjusted.";
|
||||||
errorMessage);
|
throw new InvalidLoanTransactionTypeException("transaction",
|
||||||
|
"adjustment.is.only.allowed.to.repayment.or.waiver.or.creditbalancerefund.transactions", errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionForAdjustment.reverse();
|
transactionForAdjustment.reverse();
|
||||||
@@ -3698,7 +3699,8 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final LoanTransaction loanTransaction : this.loanTransactions) {
|
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));
|
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5157,6 +5159,14 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
dataValidationErrors.add(error);
|
dataValidationErrors.add(error);
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -6062,6 +6072,40 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
return this.guaranteeAmountDerived == null ? BigDecimal.ZERO : this.guaranteeAmountDerived;
|
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,
|
public ChangedTransactionDetail makeRefundForActiveLoan(final LoanTransaction loanTransaction,
|
||||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
|
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
|
||||||
final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
|
final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
|
||||||
@@ -6179,7 +6223,8 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
|
|
||||||
LocalDate lastTransactionDate = null;
|
LocalDate lastTransactionDate = null;
|
||||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
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();
|
lastTransactionDate = transaction.getTransactionDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6841,4 +6886,5 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
public void adjustNetDisbursalAmount(BigDecimal adjustedAmount) {
|
public void adjustNetDisbursalAmount(BigDecimal adjustedAmount) {
|
||||||
this.netDisbursalAmount = adjustedAmount.subtract(this.deriveSumTotalOfChargesDueAtDisbursement());
|
this.netDisbursalAmount = adjustedAmount.subtract(this.deriveSumTotalOfChargesDueAtDisbursement());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -78,4 +78,7 @@ public interface LoanAccountDomainService {
|
|||||||
void disableStandingInstructionsLinkedToClosedLoan(Loan loan);
|
void disableStandingInstructionsLinkedToClosedLoan(Loan loan);
|
||||||
|
|
||||||
void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened);
|
void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened);
|
||||||
|
|
||||||
|
CommandProcessingResultBuilder creditBalanceRefund(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
|
||||||
|
String noteText, String externalId);
|
||||||
}
|
}
|
||||||
|
|||||||
+36
@@ -623,6 +623,41 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
return user;
|
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
|
@Override
|
||||||
public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
|
public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
|
||||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) {
|
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) {
|
||||||
@@ -794,4 +829,5 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -43,5 +43,6 @@ public enum LoanEvent {
|
|||||||
LOAN_CLOSED, //
|
LOAN_CLOSED, //
|
||||||
LOAN_EDIT_MULTI_DISBURSE_DATE, //
|
LOAN_EDIT_MULTI_DISBURSE_DATE, //
|
||||||
LOAN_REFUND, //
|
LOAN_REFUND, //
|
||||||
LOAN_FORECLOSURE;
|
LOAN_FORECLOSURE, //
|
||||||
|
LOAN_CREDIT_BALANCE_REFUND;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -297,6 +297,13 @@ public class LoanTransaction extends AbstractPersistableCustom {
|
|||||||
return applyCharge;
|
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,
|
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) {
|
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,
|
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();
|
return !isInterestWaiver() && !isChargesWaiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isNotCreditBalanceRefund() {
|
||||||
|
return !isCreditBalanceRefund();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isChargePayment() {
|
public boolean isChargePayment() {
|
||||||
return getTypeOf().isChargePayment() && isNotReversed();
|
return getTypeOf().isChargePayment() && isNotReversed();
|
||||||
}
|
}
|
||||||
@@ -616,6 +627,10 @@ public class LoanTransaction extends AbstractPersistableCustom {
|
|||||||
return getAmount(currency).isGreaterThanZero();
|
return getAmount(currency).isGreaterThanZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGreaterThanZeroAndLessThanOrEqualTo(BigDecimal totalOverpaid) {
|
||||||
|
return isNonZero() && this.amount.compareTo(totalOverpaid) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isNotZero(final MonetaryCurrency currency) {
|
public boolean isNotZero(final MonetaryCurrency currency) {
|
||||||
return !getAmount(currency).isZero();
|
return !getAmount(currency).isZero();
|
||||||
}
|
}
|
||||||
@@ -700,6 +715,10 @@ public class LoanTransaction extends AbstractPersistableCustom {
|
|||||||
return LoanTransactionType.REFUND.equals(getTypeOf()) && isNotReversed();
|
return LoanTransactionType.REFUND.equals(getTypeOf()) && isNotReversed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCreditBalanceRefund() {
|
||||||
|
return LoanTransactionType.CREDIT_BALANCE_REFUND.equals(getTypeOf()) && isNotReversed();
|
||||||
|
}
|
||||||
|
|
||||||
public void updateExternalId(final String externalId) {
|
public void updateExternalId(final String externalId) {
|
||||||
this.externalId = externalId;
|
this.externalId = externalId;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -51,7 +51,8 @@ public enum LoanTransactionType {
|
|||||||
REFUND(16, "loanTransactionType.refund"), //
|
REFUND(16, "loanTransactionType.refund"), //
|
||||||
CHARGE_PAYMENT(17, "loanTransactionType.chargePayment"), //
|
CHARGE_PAYMENT(17, "loanTransactionType.chargePayment"), //
|
||||||
REFUND_FOR_ACTIVE_LOAN(18, "loanTransactionType.refund"), //
|
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 Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
@@ -131,6 +132,9 @@ public enum LoanTransactionType {
|
|||||||
case 19:
|
case 19:
|
||||||
loanTransactionType = LoanTransactionType.INCOME_POSTING;
|
loanTransactionType = LoanTransactionType.INCOME_POSTING;
|
||||||
break;
|
break;
|
||||||
|
case 20:
|
||||||
|
loanTransactionType = LoanTransactionType.CREDIT_BALANCE_REFUND;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
loanTransactionType = LoanTransactionType.INVALID;
|
loanTransactionType = LoanTransactionType.INVALID;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+72
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -529,6 +529,9 @@ public final class LoanEventApiJsonValidator {
|
|||||||
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
||||||
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
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);
|
validatePaymentDetails(baseDataValidator, element);
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -128,6 +128,8 @@ public interface LoanReadPlatformService {
|
|||||||
|
|
||||||
LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
|
LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
|
||||||
|
|
||||||
|
LoanTransactionData retrieveCreditBalanceRefundTemplate(Long loanId);
|
||||||
|
|
||||||
Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(LoanAccountData loan);
|
Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(LoanAccountData loan);
|
||||||
|
|
||||||
Collection<Long> retrieveLoanIdsWithPendingIncomePostingTransactions();
|
Collection<Long> retrieveLoanIdsWithPendingIncomePostingTransactions();
|
||||||
|
|||||||
+24
-10
@@ -2118,24 +2118,38 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanTransactionData retrieveRefundByCashTemplate(Long loanId) {
|
public LoanTransactionData retrieveRefundByCashTemplate(Long loanId) {
|
||||||
// TODO Auto-generated method stub
|
|
||||||
this.context.authenticatedUser();
|
this.context.authenticatedUser();
|
||||||
|
|
||||||
// TODO - KW - OPTIMIZE - write simple sql query to fetch back date of
|
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||||
// possible next transaction date.
|
|
||||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
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 ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||||
|
|
||||||
final CurrencyData currencyData = applicationCurrency.toData();
|
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 LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(loanTransactionType);
|
||||||
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, currentDate, transactionAmount, null,
|
||||||
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
|
netDisbursal, null, null, null, null, null, paymentOptions, null, null, null, null, false);
|
||||||
retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, loan.getNetDisbursalAmount(), null, null, null, null,
|
|
||||||
null, paymentOptions, null, null, null, null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+2
@@ -116,4 +116,6 @@ public interface LoanWritePlatformService {
|
|||||||
|
|
||||||
CommandProcessingResult makeGLIMLoanRepayment(Long loanId, JsonCommand command);
|
CommandProcessingResult makeGLIMLoanRepayment(Long loanId, JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand command);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-1
@@ -3213,10 +3213,40 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
return installments;
|
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
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) {
|
public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) {
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
|
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
|
||||||
|
|
||||||
@@ -3400,4 +3430,5 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -411,6 +411,10 @@ public final class LoanEnumerations {
|
|||||||
optionData = new LoanTransactionEnumData(LoanTransactionType.INCOME_POSTING.getValue().longValue(),
|
optionData = new LoanTransactionEnumData(LoanTransactionType.INCOME_POSTING.getValue().longValue(),
|
||||||
LoanTransactionType.INCOME_POSTING.getCode(), "Income Posting");
|
LoanTransactionType.INCOME_POSTING.getCode(), "Income Posting");
|
||||||
break;
|
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;
|
return optionData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,4 +30,5 @@
|
|||||||
<include file="parts/0008_loan_charge_add_external_id.xml" relativeToChangelogFile="true"/>
|
<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/0009_hold_reason_savings_account.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0010_lien_allowed_on_savings_account_products.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>
|
</databaseChangeLog>
|
||||||
|
|||||||
+32
@@ -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>
|
||||||
+307
@@ -0,0 +1,307 @@
|
|||||||
|
/**
|
||||||
|
* 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.assertEquals;
|
||||||
|
|
||||||
|
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.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.CommonConstants;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.Account;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
public class ClientLoanCreditBalanceRefundIntegrationTest {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ClientLoanCreditBalanceRefundIntegrationTest.class);
|
||||||
|
|
||||||
|
private ResponseSpecification responseSpec;
|
||||||
|
private RequestSpecification requestSpec;
|
||||||
|
private LoanTransactionHelper loanTransactionHelper;
|
||||||
|
private LoanTransactionHelper loanTransactionHelperValidationError;
|
||||||
|
private JournalEntryHelper journalEntryHelper;
|
||||||
|
private AccountHelper accountHelper;
|
||||||
|
private Integer disbursedLoanID;
|
||||||
|
private static final String ACCRUAL_PERIODIC = "3";
|
||||||
|
private Account assetAccount;
|
||||||
|
private Account incomeAccount;
|
||||||
|
private Account expenseAccount;
|
||||||
|
private Account overpaymentAccount;
|
||||||
|
|
||||||
|
@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();
|
||||||
|
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||||
|
this.loanTransactionHelperValidationError = new LoanTransactionHelper(this.requestSpec, new ResponseSpecBuilder().build());
|
||||||
|
this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
|
||||||
|
this.assetAccount = this.accountHelper.createAssetAccount();
|
||||||
|
this.incomeAccount = this.accountHelper.createIncomeAccount();
|
||||||
|
this.expenseAccount = this.accountHelper.createExpenseAccount();
|
||||||
|
this.overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||||
|
this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
|
||||||
|
|
||||||
|
final String principal = "12000.00";
|
||||||
|
final String submitApproveDisburseDate = "01 January 2022";
|
||||||
|
this.disbursedLoanID = fromStartToDisburseLoan(submitApproveDisburseDate, principal, ACCRUAL_PERIODIC, assetAccount, incomeAccount,
|
||||||
|
expenseAccount, overpaymentAccount);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer createLoanProduct(final String principal, final boolean multiDisburseLoan, final String accountingRule,
|
||||||
|
final Account... accounts) {
|
||||||
|
LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||||
|
LoanProductTestBuilder builder = new LoanProductTestBuilder() //
|
||||||
|
.withPrincipal(principal) //
|
||||||
|
.withNumberOfRepayments("4") //
|
||||||
|
.withRepaymentAfterEvery("1") //
|
||||||
|
.withRepaymentTypeAsMonth() //
|
||||||
|
.withinterestRatePerPeriod("1") //
|
||||||
|
.withInterestRateFrequencyTypeAsMonths() //
|
||||||
|
.withAmortizationTypeAsEqualInstallments() //
|
||||||
|
.withInterestTypeAsDecliningBalance() //
|
||||||
|
.withAccounting(accountingRule, accounts) //
|
||||||
|
.withTranches(multiDisburseLoan);
|
||||||
|
if (multiDisburseLoan) {
|
||||||
|
builder = builder.withInterestCalculationPeriodTypeAsRepaymentPeriod(true);
|
||||||
|
builder = builder.withMaxTrancheCount("30");
|
||||||
|
}
|
||||||
|
final String loanProductJSON = builder.build(null);
|
||||||
|
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String principal, String submitDate) {
|
||||||
|
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||||
|
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
|
||||||
|
.withPrincipal(principal) //
|
||||||
|
.withLoanTermFrequency("4") //
|
||||||
|
.withLoanTermFrequencyAsMonths() //
|
||||||
|
.withNumberOfRepayments("4") //
|
||||||
|
.withRepaymentEveryAfter("1") //
|
||||||
|
.withRepaymentFrequencyTypeAsMonths() //
|
||||||
|
.withInterestRatePerPeriod("2") //
|
||||||
|
.withAmortizationTypeAsEqualInstallments() //
|
||||||
|
.withInterestTypeAsDecliningBalance() //
|
||||||
|
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
|
||||||
|
.withExpectedDisbursementDate(submitDate) //
|
||||||
|
.withSubmittedOnDate(submitDate) //
|
||||||
|
.build(clientID.toString(), loanProductID.toString(), null);
|
||||||
|
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer fromStartToDisburseLoan(String submitApproveDisburseDate, String principal, final String accountingRule,
|
||||||
|
final Account... accounts) {
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||||
|
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||||
|
|
||||||
|
boolean allowMultipleDisbursals = false;
|
||||||
|
final Integer loanProductID = createLoanProduct(principal, allowMultipleDisbursals, accountingRule, accounts);
|
||||||
|
Assertions.assertNotNull(loanProductID);
|
||||||
|
|
||||||
|
final Integer loanID = applyForLoanApplication(clientID, loanProductID, principal, submitApproveDisburseDate);
|
||||||
|
Assertions.assertNotNull(loanID);
|
||||||
|
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||||
|
|
||||||
|
LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.approveLoan(submitApproveDisburseDate, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||||
|
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||||
|
|
||||||
|
LOG.info("-------------------------------DISBURSE LOAN -------------------------------------------"); //
|
||||||
|
// String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.disburseLoan(submitApproveDisburseDate, loanID, principal);
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
return loanID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap makeRepayment(final String repaymentDate, final Float repayment) {
|
||||||
|
LOG.info("-------------Make repayment -----------");
|
||||||
|
this.loanTransactionHelper.makeRepayment(repaymentDate, repayment, disbursedLoanID);
|
||||||
|
HashMap loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, disbursedLoanID,
|
||||||
|
"status");
|
||||||
|
return loanStatusHashMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void creditBalanceRefundCanOnlyBeAppliedWhereLoanStatusIsOverpaidTest() {
|
||||||
|
HashMap loanStatusHashMap = makeRepayment("06 January 2022", 2000.00f); // not full payment
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
|
||||||
|
final String creditBalanceRefundDate = "09 January 2022";
|
||||||
|
final Float refund = 1000.00f;
|
||||||
|
final String externalId = null;
|
||||||
|
ArrayList<HashMap> cbrErrors = (ArrayList<HashMap>) loanTransactionHelperValidationError
|
||||||
|
.creditBalanceRefund(creditBalanceRefundDate, refund, externalId, disbursedLoanID, CommonConstants.RESPONSE_ERROR);
|
||||||
|
|
||||||
|
assertEquals("error.msg.loan.credit.balance.refund.account.is.not.overpaid",
|
||||||
|
cbrErrors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
|
||||||
|
|
||||||
|
// ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
|
||||||
|
// this.responseSpec, loanID);
|
||||||
|
// final int loanScheduleLineCount = loanSchedule.size();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cantRefundMoreThanOverpaidTest() {
|
||||||
|
HashMap loanStatusHashMap = makeRepayment("06 January 2022", 20000.00f); // overpayment
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
|
||||||
|
|
||||||
|
final String creditBalanceRefundDate = "09 January 2022";
|
||||||
|
Float refund = 10000.00f;
|
||||||
|
final String externalId = null;
|
||||||
|
ArrayList<HashMap> cbrErrors = (ArrayList<HashMap>) loanTransactionHelperValidationError
|
||||||
|
.creditBalanceRefund(creditBalanceRefundDate, refund, externalId, disbursedLoanID, CommonConstants.RESPONSE_ERROR);
|
||||||
|
|
||||||
|
assertEquals("error.msg.transactionAmount.invalid.must.be.>zero.and<=overpaidamount",
|
||||||
|
cbrErrors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
|
||||||
|
|
||||||
|
refund = (float) -1.00;
|
||||||
|
cbrErrors = (ArrayList<HashMap>) loanTransactionHelperValidationError.creditBalanceRefund(creditBalanceRefundDate, refund,
|
||||||
|
externalId, disbursedLoanID, CommonConstants.RESPONSE_ERROR);
|
||||||
|
assertEquals("validation.msg.loan.transaction.transactionAmount.not.greater.than.zero",
|
||||||
|
cbrErrors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fullRefundChangesStatusToClosedObligationMetTest() {
|
||||||
|
HashMap loanStatusHashMap = makeRepayment("06 January 2022", 20000.00f); // overpayment
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
|
||||||
|
|
||||||
|
final Float totalOverpaid = (Float) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, disbursedLoanID,
|
||||||
|
"totalOverpaid");
|
||||||
|
|
||||||
|
final String creditBalanceRefundDate = "09 January 2022";
|
||||||
|
final String externalId = null;
|
||||||
|
loanTransactionHelper.creditBalanceRefund(creditBalanceRefundDate, totalOverpaid, externalId, disbursedLoanID, null);
|
||||||
|
loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, disbursedLoanID,
|
||||||
|
"status");
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
|
||||||
|
|
||||||
|
final Float floatZero = 0.0f;
|
||||||
|
Float totalOverpaidAtEnd = (Float) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, disbursedLoanID,
|
||||||
|
"totalOverpaid");
|
||||||
|
if (totalOverpaidAtEnd == null) {
|
||||||
|
totalOverpaidAtEnd = floatZero;
|
||||||
|
}
|
||||||
|
assertEquals(totalOverpaidAtEnd, floatZero);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void partialRefundKeepsOverpaidStatusTest() {
|
||||||
|
HashMap loanStatusHashMap = makeRepayment("06 January 2022", 20000.00f); // overpayment
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
|
||||||
|
|
||||||
|
final Float refund = 5000.00f; // partial refund
|
||||||
|
|
||||||
|
final String creditBalanceRefundDate = "09 January 2022";
|
||||||
|
final String externalId = null;
|
||||||
|
loanTransactionHelper.creditBalanceRefund(creditBalanceRefundDate, refund, externalId, disbursedLoanID, null);
|
||||||
|
loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, disbursedLoanID,
|
||||||
|
"status");
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newCreditBalanceRefundSavesExternalIdTest() {
|
||||||
|
|
||||||
|
HashMap loanStatusHashMap = makeRepayment("06 January 2022", 20000.00f); // overpayment
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
|
||||||
|
|
||||||
|
final Float refund = 1000.00f; // partial refund
|
||||||
|
final String creditBalanceRefundDate = "09 January 2022";
|
||||||
|
final String externalId = "cbrextID" + disbursedLoanID.toString();
|
||||||
|
Integer resourceId = (Integer) loanTransactionHelper.creditBalanceRefund(creditBalanceRefundDate, refund, externalId,
|
||||||
|
disbursedLoanID, "resourceId");
|
||||||
|
Assertions.assertNotNull(resourceId);
|
||||||
|
|
||||||
|
HashMap creditBalanceRefundMap = this.loanTransactionHelper.getLoanTransactionDetails(disbursedLoanID, resourceId);
|
||||||
|
Assertions.assertNotNull(creditBalanceRefundMap.get("externalId"));
|
||||||
|
Assertions.assertEquals(creditBalanceRefundMap.get("externalId"), externalId, "Incorrect External Id Saved");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newCreditBalanceRefundFindsDuplicateExternalIdTest() {
|
||||||
|
|
||||||
|
HashMap loanStatusHashMap = makeRepayment("06 January 2022", 20000.00f); // overpayment
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
|
||||||
|
|
||||||
|
final Float refund = 1000.00f; // partial refund
|
||||||
|
final String creditBalanceRefundDate = "09 January 2022";
|
||||||
|
final String externalId = "cbrextID" + disbursedLoanID.toString();
|
||||||
|
final Integer resourceId = (Integer) loanTransactionHelper.creditBalanceRefund(creditBalanceRefundDate, refund, externalId,
|
||||||
|
disbursedLoanID, "resourceId");
|
||||||
|
Assertions.assertNotNull(resourceId);
|
||||||
|
|
||||||
|
final Float refund2 = 10.00f; // partial refund
|
||||||
|
final String creditBalanceRefundDate2 = "10 January 2022";
|
||||||
|
ArrayList<HashMap> cbrErrors = (ArrayList<HashMap>) loanTransactionHelperValidationError
|
||||||
|
.creditBalanceRefund(creditBalanceRefundDate2, refund2, externalId, disbursedLoanID, CommonConstants.RESPONSE_ERROR);
|
||||||
|
assertEquals("error.msg.loan.creditBalanceRefund.duplicate.externalId",
|
||||||
|
cbrErrors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newCreditBalanceRefundCreatesCorrectJournalEntriesForPeriodicAccrualsTest() {
|
||||||
|
|
||||||
|
HashMap loanStatusHashMap = makeRepayment("06 January 2022", 20000.00f); // overpayment
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
|
||||||
|
|
||||||
|
final Float refund = 1000.00f; // partial refund
|
||||||
|
final String creditBalanceRefundDate = "09 January 2022";
|
||||||
|
final String externalId = null;
|
||||||
|
final Integer resourceId = (Integer) loanTransactionHelper.creditBalanceRefund(creditBalanceRefundDate, refund, externalId,
|
||||||
|
disbursedLoanID, "resourceId");
|
||||||
|
Assertions.assertNotNull(resourceId);
|
||||||
|
|
||||||
|
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, creditBalanceRefundDate,
|
||||||
|
new JournalEntry(refund, JournalEntry.TransactionType.DEBIT));
|
||||||
|
this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, creditBalanceRefundDate,
|
||||||
|
new JournalEntry(refund, JournalEntry.TransactionType.CREDIT));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -46,9 +46,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
* repayments and verifying accounting transactions
|
* repayments and verifying accounting transactions
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
public class ClientLoanIntegrationMultipleDisbursementsTest {
|
public class ClientLoanMultipleDisbursementsIntegrationTest {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClientLoanIntegrationMultipleDisbursementsTest.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ClientLoanMultipleDisbursementsIntegrationTest.class);
|
||||||
|
|
||||||
public static final String MINIMUM_OPENING_BALANCE = "1000.0";
|
public static final String MINIMUM_OPENING_BALANCE = "1000.0";
|
||||||
public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
|
public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
|
||||||
+26
@@ -63,6 +63,7 @@ public class LoanTransactionHelper {
|
|||||||
private static final String WRITE_OFF_LOAN_COMMAND = "writeoff";
|
private static final String WRITE_OFF_LOAN_COMMAND = "writeoff";
|
||||||
private static final String WAIVE_INTEREST_COMMAND = "waiveinterest";
|
private static final String WAIVE_INTEREST_COMMAND = "waiveinterest";
|
||||||
private static final String MAKE_REPAYMENT_COMMAND = "repayment";
|
private static final String MAKE_REPAYMENT_COMMAND = "repayment";
|
||||||
|
private static final String CREDIT_BALANCE_REFUND_COMMAND = "creditBalanceRefund";
|
||||||
private static final String WITHDRAW_LOAN_APPLICATION_COMMAND = "withdrawnByApplicant";
|
private static final String WITHDRAW_LOAN_APPLICATION_COMMAND = "withdrawnByApplicant";
|
||||||
private static final String RECOVER_FROM_GUARANTORS_COMMAND = "recoverGuarantees";
|
private static final String RECOVER_FROM_GUARANTORS_COMMAND = "recoverGuarantees";
|
||||||
private static final String MAKE_REFUND_BY_CASH_COMMAND = "refundByCash";
|
private static final String MAKE_REFUND_BY_CASH_COMMAND = "refundByCash";
|
||||||
@@ -332,6 +333,12 @@ public class LoanTransactionHelper {
|
|||||||
return resourceId;
|
return resourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Object creditBalanceRefund(final String date, final Float amountToBePaid, final String externalId, final Integer loanID,
|
||||||
|
String jsonAttributeToGetback) {
|
||||||
|
return performLoanTransaction(createLoanTransactionURL(CREDIT_BALANCE_REFUND_COMMAND, loanID),
|
||||||
|
getCreditBalanceRefundBodyAsJSON(date, amountToBePaid, externalId), jsonAttributeToGetback);
|
||||||
|
}
|
||||||
|
|
||||||
public HashMap makeRepayment(final String date, final Float amountToBePaid, final Integer loanID) {
|
public HashMap makeRepayment(final String date, final Float amountToBePaid, final Integer loanID) {
|
||||||
return (HashMap) performLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID),
|
return (HashMap) performLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID),
|
||||||
getRepaymentBodyAsJSON(date, amountToBePaid), "");
|
getRepaymentBodyAsJSON(date, amountToBePaid), "");
|
||||||
@@ -425,6 +432,12 @@ public class LoanTransactionHelper {
|
|||||||
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, "");
|
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap getLoanTransactionDetails(final Integer loanId, final Integer txnId) {
|
||||||
|
final String GET_LOAN_CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/" + txnId + "?"
|
||||||
|
+ Utils.TENANT_IDENTIFIER;
|
||||||
|
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, "");
|
||||||
|
}
|
||||||
|
|
||||||
public HashMap getPostDatedCheck(final Integer loanId, final Integer installmentId) {
|
public HashMap getPostDatedCheck(final Integer loanId, final Integer installmentId) {
|
||||||
final String GET_POST_DATED_TRANS_URL = "/fineract-provider/api/v1/loans/" + loanId + "/postdatedchecks/" + installmentId + "?"
|
final String GET_POST_DATED_TRANS_URL = "/fineract-provider/api/v1/loans/" + loanId + "/postdatedchecks/" + installmentId + "?"
|
||||||
+ Utils.TENANT_IDENTIFIER;
|
+ Utils.TENANT_IDENTIFIER;
|
||||||
@@ -500,6 +513,19 @@ public class LoanTransactionHelper {
|
|||||||
return new Gson().toJson(map);
|
return new Gson().toJson(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getCreditBalanceRefundBodyAsJSON(final String transactionDate, final Float transactionAmount, final String externalId) {
|
||||||
|
final HashMap<String, String> map = new HashMap<>();
|
||||||
|
map.put("locale", "en");
|
||||||
|
map.put("dateFormat", "dd MMMM yyyy");
|
||||||
|
map.put("transactionDate", transactionDate);
|
||||||
|
map.put("transactionAmount", transactionAmount.toString());
|
||||||
|
map.put("note", "Credit Balance Refund Made!!!");
|
||||||
|
if (externalId != null) {
|
||||||
|
map.put("externalId", externalId);
|
||||||
|
}
|
||||||
|
return new Gson().toJson(map);
|
||||||
|
}
|
||||||
|
|
||||||
private String getRepaymentBodyAsJSON(final String transactionDate, final Float transactionAmount) {
|
private String getRepaymentBodyAsJSON(final String transactionDate, final Float transactionAmount) {
|
||||||
final HashMap<String, String> map = new HashMap<>();
|
final HashMap<String, String> map = new HashMap<>();
|
||||||
map.put("locale", "en");
|
map.put("locale", "en");
|
||||||
|
|||||||
Reference in New Issue
Block a user