+
+
Retrieve Refund of an Active Loan by Transfer Template:
+
Example Requests :
+
accounttransfers/templateRefundByTransfer?fromAccountId=2&fromAccountType=1&
+ toAccountId=1&toAccountType=2&toClientId=1&toOfficeId=1
+
+
+
+ GET https://DomainName/api/v1/accounttransfers/templateRefundByTransfer
+
+{
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 0,
+ "inMultiplesOf": 0,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "transferAmount": 130,
+ "transferDate": [
+ 2014,
+ 11,
+ 1
+ ],
+ "fromOffice": {
+ "id": 1,
+ "name": "Head Office",
+ "nameDecorated": "Head Office",
+ "externalId": "1",
+ "openingDate": [
+ 2009,
+ 1,
+ 1
+ ],
+ "hierarchy": "."
+ },
+ "fromClient": {
+ "id": 1,
+ "accountNo": "000000001",
+ "status": {
+ "id": 300,
+ "code": "clientStatusType.active",
+ "value": "Active"
+ },
+ "active": true,
+ "activationDate": [
+ 2012,
+ 2,
+ 1
+ ],
+ "firstname": "Daniel",
+ "lastname": "Owusu",
+ "displayName": "Daniel Owusu",
+ "gender": {},
+ "clientType": {},
+ "clientClassification": {},
+ "officeId": 1,
+ "officeName": "Head Office",
+ "timeline": {
+ "submittedOnDate": [
+ 2012,
+ 2,
+ 1
+ ],
+ "submittedByUsername": "mifos",
+ "submittedByFirstname": "App",
+ "submittedByLastname": "Administrator",
+ "activatedOnDate": [
+ 2012,
+ 2,
+ 1
+ ],
+ "activatedByUsername": "mifos",
+ "activatedByFirstname": "App",
+ "activatedByLastname": "Administrator"
+ },
+ "groups": []
+ },
+ "fromAccountType": {
+ "id": 1,
+ "code": "accountType.loan",
+ "value": "Loan Account"
+ },
+ "fromAccount": {
+ "id": 2,
+ "accountNo": "000000002",
+ "clientId": 1,
+ "clientName": "Daniel Owusu",
+ "productId": 1,
+ "productName": "CTRL",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 0,
+ "inMultiplesOf": 0,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "amtForTransfer": 130
+ },
+ "toOffice": {
+ "id": 1,
+ "name": "Head Office",
+ "nameDecorated": "Head Office",
+ "externalId": "1",
+ "openingDate": [
+ 2009,
+ 1,
+ 1
+ ],
+ "hierarchy": "."
+ },
+ "toClient": {
+ "id": 1,
+ "displayName": "Daniel Owusu",
+ "officeId": 1,
+ "officeName": "Head Office"
+ },
+ "toAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "toAccount": {
+ "id": 1,
+ "accountNo": "000000001",
+ "clientId": 1,
+ "clientName": "Daniel Owusu",
+ "productId": 1,
+ "productName": "TEST",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 0,
+ "inMultiplesOf": 0,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ }
+ },
+ "fromOfficeOptions": [
+ {
+ "id": 1,
+ "name": "Head Office",
+ "nameDecorated": "Head Office"
+ }
+ ],
+ "fromClientOptions": [
+ {
+ "id": 1,
+ "displayName": "Daniel Owusu",
+ "officeId": 1,
+ "officeName": "Head Office"
+ }
+ ],
+ "fromAccountTypeOptions": [
+ {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ {
+ "id": 1,
+ "code": "accountType.loan",
+ "value": "Loan Account"
+ }
+ ],
+ "fromAccountOptions": [
+ {
+ "id": 2,
+ "accountNo": "000000002",
+ "clientId": 1,
+ "clientName": "Daniel Owusu",
+ "productId": 1,
+ "productName": "CTRL",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 0,
+ "inMultiplesOf": 0,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ }
+ }
+ ],
+ "toOfficeOptions": [
+ {
+ "id": 1,
+ "name": "Head Office",
+ "nameDecorated": "Head Office"
+ }
+ ],
+ "toClientOptions": [
+ {
+ "id": 1,
+ "displayName": "Daniel Owusu",
+ "officeId": 1,
+ "officeName": "Head Office"
+ }
+ ],
+ "toAccountTypeOptions": [
+ {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ }
+ ],
+ "toAccountOptions": [
+ {
+ "id": 1,
+ "accountNo": "000000001",
+ "clientId": 1,
+ "clientName": "Daniel Owusu",
+ "productId": 1,
+ "productName": "TEST",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 0,
+ "inMultiplesOf": 0,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ }
+ }
+ ]
+}
+
+
+
+
+
+
+
+
Refund of an Active Loan by Transfer
+
Ability to refund an active loan by transferring to a savings account.
+
+
+ POST https://Domain Name/api/v1/accounttransfers
+ POST refundByTransfer/
+Content-Type: application/json
+No Request Body:
+{
+ "fromAccountId": "2",
+ "fromAccountType": 1,
+ "toOfficeId": 1,
+ "toClientId": 1,
+ "toAccountType": 2,
+ "toAccountId": 1,
+ "transferAmount": 130,
+ "transferDate": "31 October 2014",
+ "transferDescription": "Transfer refund to my savings account",
+ "locale": "en",
+ "dateFormat": "dd MMMM yyyy",
+ "fromClientId": 1,
+ "fromOfficeId": 1
+
+}
+
+
+{
+ "savingsId": 1,
+ "resourceId": 1
}
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java
index 6b947db27..3e8f2153b 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java
@@ -35,6 +35,7 @@ import org.mifosplatform.integrationtests.common.loans.LoanApplicationTestBuilde
import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanStatusChecker;
import org.mifosplatform.integrationtests.common.loans.LoanTransactionHelper;
+import org.mifosplatform.integrationtests.common.savings.AccountTransferHelper;
import org.mifosplatform.integrationtests.common.savings.SavingsAccountHelper;
import org.mifosplatform.integrationtests.common.savings.SavingsProductHelper;
import org.mifosplatform.integrationtests.common.savings.SavingsStatusChecker;
@@ -68,6 +69,8 @@ public class ClientLoanIntegrationTest {
private AccountHelper accountHelper;
private SchedulerJobHelper schedulerJobHelper;
private PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper;
+ private SavingsAccountHelper savingsAccountHelper;
+ private AccountTransferHelper accountTransferHelper;
@Before
public void setup() {
@@ -888,6 +891,23 @@ public class ClientLoanIntegrationTest {
.currencyDetails(digitsAfterDecimal, inMultiplesOf).build(null);
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
+
+ private Integer createLoanProduct(final String inMultiplesOf, final String digitsAfterDecimal, final String repaymentStrategy,
+ final String accountingRule, final Account... accounts) {
+ System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+ final String loanProductJSON = new LoanProductTestBuilder() //
+ .withPrincipal("10000000.00") //
+ .withNumberOfRepayments("24") //
+ .withRepaymentAfterEvery("1") //
+ .withRepaymentTypeAsMonth() //
+ .withinterestRatePerPeriod("2") //
+ .withInterestRateFrequencyTypeAsMonths() //
+ .withRepaymentStrategy(repaymentStrategy) //
+ .withAmortizationTypeAsEqualPrincipalPayment() //
+ .withInterestTypeAsDecliningBalance() //
+ .currencyDetails(digitsAfterDecimal, inMultiplesOf).withAccounting(accountingRule, accounts).build(null);
+ return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ }
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String graceOnPrincipalPayment) {
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
@@ -989,6 +1009,32 @@ public class ClientLoanIntegrationTest {
.withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
}
+
+ private Integer applyForLoanApplicationWithPaymentStrategyAndPastMonth(final Integer clientID, final Integer loanProductID, List
charges,
+ final String savingsId, String principal, final String repaymentStrategy, final int month) {
+ System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+
+ Calendar fourMonthsfromNowCalendar = Calendar.getInstance();
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, month);
+
+ String fourMonthsfromNow = new SimpleDateFormat("dd MMMMMM yyyy").format(fourMonthsfromNowCalendar.getTime());
+ final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+ .withPrincipal(principal) //
+ .withLoanTermFrequency("6") //
+ .withLoanTermFrequencyAsMonths() //
+ .withNumberOfRepayments("6") //
+ .withRepaymentEveryAfter("1") //
+ .withRepaymentFrequencyTypeAsMonths() //
+ .withInterestRatePerPeriod("2") //
+ .withAmortizationTypeAsEqualInstallments() //
+ .withInterestTypeAsFlatBalance() //
+ .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+ .withExpectedDisbursementDate(fourMonthsfromNow) //
+ .withSubmittedOnDate(fourMonthsfromNow) //
+ .withwithRepaymentStrategy(repaymentStrategy) //
+ .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
+ return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+ }
private void verifyLoanRepaymentSchedule(final ArrayList loanSchedule) {
System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
@@ -3756,4 +3802,543 @@ public class ClientLoanIntegrationTest {
index++;
}
}
+
+ /***
+ * Test case to verify Mifos Style payment strategy
+ */
+ @Test
+ public void testLoanRefundByCashCashBasedAccounting() {
+ this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+ Calendar fourMonthsfromNowCalendar = Calendar.getInstance();
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
+
+ String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+ final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+ ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+ /***
+ * Create loan product with MIFOS STYLE strategy
+ */
+
+ final Account assetAccount = this.accountHelper.createAssetAccount();
+ final Account incomeAccount = this.accountHelper.createIncomeAccount();
+ final Account expenseAccount = this.accountHelper.createExpenseAccount();
+ final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+ final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, CASH_BASED,
+ assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+ Assert.assertNotNull(loanProductID);
+
+ /***
+ * Apply for loan application and verify loan status
+ */
+ final String savingsId = null;
+ final String principal = "12,000.00";
+
+ // Add charges with payment mode regular
+ List charges = new ArrayList<>();
+
+ Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+ ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+ addCharges(charges, flatInstallmentFee, "50", null);
+
+ final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, savingsId, principal,
+ LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, -4);
+ Assert.assertNotNull(loanID);
+ HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+ loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+ loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ final JournalEntry[] assetAccountInitialEntry = {
+ new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
+
+ ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap firstInstallment = loanSchedule.get(1);
+ validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+
+ // Make payment for installment #1
+
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ firstInstallment = loanSchedule.get(1);
+ validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+ // Make payment for installment #2
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow,
+ new JournalEntry(Float.valueOf("2290"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2000"),
+ JournalEntry.TransactionType.CREDIT));
+ this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, twoMonthsfromNow,
+ new JournalEntry(Float.valueOf("50"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ Map secondInstallment = loanSchedule.get(2);
+ validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+ // Make payment for installment #3
+ //Pay 2290 more than expected
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow,
+ new JournalEntry(Float.valueOf("4580"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("4000"),
+ JournalEntry.TransactionType.CREDIT));
+ this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, oneMonthfromNow,
+ new JournalEntry(Float.valueOf("100"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap thirdInstallment = loanSchedule.get(3);
+ validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+
+ // Make refund of 20
+ //max 2290 to refund. Pay 20 means only principal
+ //mifos style refund order(principal, interest, fees and penalties
+ //paid: principal 2000, interest 240, fees 50, penalty 0
+ //refund 20 means paid: principal 1980, interest 240, fees 50, penalty 0
+
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+ final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("20"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now,
+ new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"),
+ JournalEntry.TransactionType.DEBIT));
+
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap fourthInstallment = loanSchedule.get(4);
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+ // Make refund of 2000
+ //max 2270 to refund. Pay 2000 means only principal
+ //paid: principal 1980, interest 240, fees 50, penalty 0
+ //refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
+
+ this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("2000"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now,
+ new JournalEntry(Float.valueOf("2000"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"),
+ JournalEntry.TransactionType.DEBIT));
+
+ this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now,
+ new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ fourthInstallment = loanSchedule.get(4);
+ validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+ validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+ }
+
+ /***
+ * Test case to verify Mifos style payment strategy
+ */
+ @Test
+ public void testLoanRefundByCashAccrualBasedAccounting() {
+ this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+
+ Calendar fourMonthsfromNowCalendar = Calendar.getInstance();
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
+
+ String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+ final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+ ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+ /***
+ * Create loan product with MIFOS STYLE strategy
+ */
+
+ final Account assetAccount = this.accountHelper.createAssetAccount();
+ final Account incomeAccount = this.accountHelper.createIncomeAccount();
+ final Account expenseAccount = this.accountHelper.createExpenseAccount();
+ final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+ final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, ACCRUAL_UPFRONT,
+ assetAccount, incomeAccount, expenseAccount, overpaymentAccount);//, LoanProductTestBuilder.EQUAL_INSTALLMENTS,
+ //LoanProductTestBuilder.FLAT_BALANCE);
+ Assert.assertNotNull(loanProductID);
+
+ /***
+ * Apply for loan application and verify loan status
+ */
+ final String savingsId = null;
+ final String principal = "12,000.00";
+
+ // Add charges with payment mode regular
+ List charges = new ArrayList<>();
+
+ Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+ ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+ addCharges(charges, flatInstallmentFee, "50", null);
+
+ final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, savingsId, principal,
+ LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, -4);
+ Assert.assertNotNull(loanID);
+ HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+ loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+ loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ final JournalEntry[] assetAccountInitialEntry = {
+ new JournalEntry(Float.valueOf("1440"), JournalEntry.TransactionType.DEBIT),
+ new JournalEntry(Float.valueOf("300.00"), JournalEntry.TransactionType.DEBIT),
+ new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
+
+ ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap firstInstallment = loanSchedule.get(1);
+ validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+
+ // Make payment for installment #1
+
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ firstInstallment = loanSchedule.get(1);
+ validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+ // Make payment for installment #2
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow,
+ new JournalEntry(Float.valueOf("2290"), JournalEntry.TransactionType.DEBIT),
+ new JournalEntry(Float.valueOf("50"), JournalEntry.TransactionType.CREDIT),new JournalEntry(Float.valueOf("240"),
+ JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2000"),
+ JournalEntry.TransactionType.CREDIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ Map secondInstallment = loanSchedule.get(2);
+ validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+ // Make payment for installment #3
+ //Pay 2290 more than expected
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow,
+ new JournalEntry(Float.valueOf("4580"), JournalEntry.TransactionType.DEBIT),
+ new JournalEntry(Float.valueOf("100"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("4000"),JournalEntry.TransactionType.CREDIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap thirdInstallment = loanSchedule.get(3);
+ validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+
+ // Make refund of 20
+ //max 2290 to refund. Pay 20 means only principal
+ //mifos style refund order(principal, interest, fees and penalties
+ //paid: principal 2000, interest 240, fees 50, penalty 0
+ //refund 20 means paid: principal 1980, interest 240, fees 50, penalty 0
+
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+ final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("20"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now,
+ new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"),
+ JournalEntry.TransactionType.DEBIT));
+
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap fourthInstallment = loanSchedule.get(4);
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+ // Make refund of 2000
+ //max 2270 to refund. Pay 2000 means only principal
+ //paid: principal 1980, interest 240, fees 50, penalty 0
+ //refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
+
+ this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("2000"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now,
+ new JournalEntry(Float.valueOf("2000"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"),
+ JournalEntry.TransactionType.DEBIT));
+
+ this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now,
+ new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ fourthInstallment = loanSchedule.get(4);
+ validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+ validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+ }
+
+ @Test
+ public void testLoanRefundByTransferCashBasedAccounting() {
+ this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+ this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+ this.accountTransferHelper = new AccountTransferHelper(this.requestSpec, this.responseSpec);
+
+ Calendar fourMonthsfromNowCalendar = Calendar.getInstance();
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
+
+ String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+ final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+ ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+
+ final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE);
+ Assert.assertNotNull(savingsProductID);
+
+ final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+ Assert.assertNotNull(savingsProductID);
+
+ HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
+ ACCOUNT_TYPE_INDIVIDUAL);
+ Assert.assertTrue(modifications.containsKey("submittedOnDate"));
+
+ HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
+ SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+ savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
+ SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+ savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
+ SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+ /***
+ * Create loan product with MIFOS STYLE strategy
+ */
+
+ final Account assetAccount = this.accountHelper.createAssetAccount();
+ final Account incomeAccount = this.accountHelper.createIncomeAccount();
+ final Account expenseAccount = this.accountHelper.createExpenseAccount();
+ final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+ final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, CASH_BASED,
+ assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+ Assert.assertNotNull(loanProductID);
+
+ /***
+ * Apply for loan application and verify loan status
+ */
+
+ final String principal = "12,000.00";
+
+ // Add charges with payment mode regular
+ List charges = new ArrayList<>();
+
+ Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
+ ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
+ addCharges(charges, flatInstallmentFee, "50", null);
+
+ final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, null, principal,
+ LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, -4);
+ Assert.assertNotNull(loanID);
+ HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
+ loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
+ loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ final JournalEntry[] assetAccountInitialEntry = {
+ new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
+
+ ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap firstInstallment = loanSchedule.get(1);
+ validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+
+ // Make payment for installment #1
+
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ firstInstallment = loanSchedule.get(1);
+ validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
+
+ // Make payment for installment #2
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow,
+ new JournalEntry(Float.valueOf("2290"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2000"),
+ JournalEntry.TransactionType.CREDIT));
+ this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, twoMonthsfromNow,
+ new JournalEntry(Float.valueOf("50"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ Map secondInstallment = loanSchedule.get(2);
+ validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
+
+ // Make payment for installment #3
+ //Pay 2290 more than expected
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+
+ final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow,
+ new JournalEntry(Float.valueOf("4580"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("4000"),
+ JournalEntry.TransactionType.CREDIT));
+ this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, oneMonthfromNow,
+ new JournalEntry(Float.valueOf("100"), JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap thirdInstallment = loanSchedule.get(3);
+ validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
+
+ // Make refund of 20
+ //max 2290 to refund. Pay 20 means only principal
+ //mifos style refund order(principal, interest, fees and penalties
+ //paid: principal 2000, interest 240, fees 50, penalty 0
+ //refund 20 means paid: principal 1980, interest 240, fees 50, penalty 0
+
+ Float TRANSFER_AMOUNT = 20f;
+
+ fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
+ final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
+
+ final String FROM_LOAN_ACCOUNT_TYPE = "1";
+ final String TO_SAVINGS_ACCOUNT_TYPE = "2";
+
+ this.accountTransferHelper.refundLoanByTransfer(now, clientID, loanID, clientID, savingsId, FROM_LOAN_ACCOUNT_TYPE,
+ TO_SAVINGS_ACCOUNT_TYPE, TRANSFER_AMOUNT.toString());
+
+ Float toSavingsBalance = new Float(MINIMUM_OPENING_BALANCE);
+
+ HashMap toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+ toSavingsBalance += TRANSFER_AMOUNT;
+
+ // Verifying toSavings Account Balance after Account Transfer
+ assertEquals("Verifying From Savings Account Balance after Account Transfer", toSavingsBalance,
+ toSavingsSummaryAfter.get("accountBalance"));
+
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now,
+ new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"),
+ JournalEntry.TransactionType.DEBIT));
+
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ HashMap fourthInstallment = loanSchedule.get(4);
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+ // Make refund of 2000
+ //max 2270 to refund. Pay 2000 means only principal
+ //paid: principal 1980, interest 240, fees 50, penalty 0
+ //refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
+ //final String now = Utils.convertDate(fourMonthsfromNowCalendar);
+
+ TRANSFER_AMOUNT = 2000f;
+
+ this.accountTransferHelper.refundLoanByTransfer(now, clientID, loanID, clientID, savingsId, FROM_LOAN_ACCOUNT_TYPE,
+ TO_SAVINGS_ACCOUNT_TYPE, TRANSFER_AMOUNT.toString());
+
+ toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
+
+ toSavingsBalance += TRANSFER_AMOUNT;
+
+ // Verifying toSavings Account Balance after Account Transfer
+ assertEquals("Verifying From Savings Account Balance after Account Transfer", toSavingsBalance,
+ toSavingsSummaryAfter.get("accountBalance"));
+
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now,
+ new JournalEntry(Float.valueOf("2000"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"),
+ JournalEntry.TransactionType.DEBIT));
+
+ this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now,
+ new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
+
+ loanSchedule.clear();
+ loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+ fourthInstallment = loanSchedule.get(4);
+ validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
+ validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
+ validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
+ validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
+
+ }
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/Utils.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/Utils.java
index ba84a7506..ae7e31429 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/Utils.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/Utils.java
@@ -13,6 +13,7 @@ import static org.junit.Assert.fail;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Calendar;
import java.util.Locale;
import java.util.Random;
@@ -115,5 +116,9 @@ public class Utils {
public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
return randomStringGenerator(prefix, lenOfRandomSuffix);
}
+
+ public static String convertDateToURLFormat(final Calendar dateToBeConvert) {
+ return new SimpleDateFormat("dd MMMMMM yyyy").format(dateToBeConvert.getTime());
+ }
}
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java
index 207d4e3ce..d562704a6 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java
@@ -35,6 +35,7 @@ public class LoanTransactionHelper {
private static final String WAIVE_INTEREST_COMMAND = "waiveinterest";
private static final String MAKE_REPAYMENT_COMMAND = "repayment";
private static final String WITHDRAW_LOAN_APPLICATION_COMMAND = "withdrawnByApplicant";
+ private static final String MAKE_REFUND_BY_CASH_COMMAND = "refundByCash";
public LoanTransactionHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
this.requestSpec = requestSpec;
@@ -352,6 +353,10 @@ public class LoanTransactionHelper {
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "");
return response;
}
+
+ private String createLoanRefundTransferURL() {
+ return "/mifosng-provider/api/v1/accounttransfers/refundByTransfer?tenantIdentifier=default";
+ }
public void verifyRepaymentScheduleEntryFor(final int repaymentNumber, final float expectedPrincipalOutstanding, final Integer loanID) {
System.out.println("---------------------------GETTING LOAN REPAYMENT SCHEDULE--------------------------------");
@@ -389,4 +394,44 @@ public class LoanTransactionHelper {
assertTrue("No Accrual entries are posted", isTransactionFound);
}
+
+ public HashMap makeRefundByCash(final String date, final Float amountToBeRefunded, final Integer loanID) {
+ return performLoanTransaction(createLoanTransactionURL(MAKE_REFUND_BY_CASH_COMMAND, loanID),
+ getRefundByCashBodyAsJSON(date, amountToBeRefunded));
+ }
+
+ public HashMap makeRefundByTransfer(final Integer fromAccountId, final Integer toClientId, final Integer toAccountId,
+ final Integer fromClientId, final String date, final Float amountToBeRefunded) {
+ return performLoanTransaction(createLoanRefundTransferURL(),
+ getRefundByTransferBodyAsJSON(fromAccountId, toClientId, toAccountId, fromClientId, date, amountToBeRefunded));
+ }
+
+ private String getRefundByCashBodyAsJSON(final String transactionDate, final Float transactionAmount) {
+ final HashMap 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", "Refund Made!!!");
+ return new Gson().toJson(map);
+ }
+
+ private String getRefundByTransferBodyAsJSON(final Integer fromAccountId, final Integer toClientId, final Integer toAccountId,
+ final Integer fromClientId, final String transactionDate, final Float transactionAmount) {
+ final HashMap map = new HashMap<>();
+ map.put("fromAccountId", fromAccountId.toString());
+ map.put("fromAccountType", "1");
+ map.put("toOfficeId", "1");
+ map.put("toClientId", toClientId.toString());
+ map.put("toAccountType", "2");
+ map.put("toAccountId", toAccountId.toString());
+ map.put("dateFormat", "dd MMMM yyyy");
+ map.put("transferDate", transactionDate);
+ map.put("transferAmount", transactionAmount.toString());
+ map.put("transferDescription", "Refund Made!!!");
+ map.put("fromClientId", fromClientId.toString());
+ map.put("fromOfficeId", "1");
+ map.put("locale", "en");
+ return new Gson().toJson(map);
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/AccountTransferHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/AccountTransferHelper.java
index 830469371..4e58d39d8 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/AccountTransferHelper.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/AccountTransferHelper.java
@@ -16,6 +16,7 @@ import com.jayway.restassured.specification.ResponseSpecification;
public class AccountTransferHelper {
private static final String ACCOUNT_TRANSFER_URL = "/mifosng-provider/api/v1/accounttransfers";
+ private static final String LOAN_REFUND_BY_TRANSFER_URL = "/mifosng-provider/api/v1/accounttransfers/refundByTransfer";
private static final String LOCALE = "en_GB";
private static final String OFFICE_ID = "1";
private static final String TRANSFER_DESCRIPTION = "Transfer";
@@ -70,4 +71,15 @@ public class AccountTransferHelper {
return Utils.performServerPost(this.requestSpec, this.responseSpec, ACCOUNT_TRANSFER_URL + "?" + Utils.TENANT_IDENTIFIER,
accountTransferJSON, "savingsId");
}
+
+ public Integer refundLoanByTransfer(final String date, final Integer fromClientId, final Integer fromAccountId, final Integer toClientId,
+ final Integer toAccountId, final String fromAccountType, final String toAccountType, final String transferAmount) {
+ System.out.println("--------------------------------ACCOUNT TRANSFER--------------------------------");
+ final String accountTransferJSON = new AccountTransferHelper(this.requestSpec, this.responseSpec) //
+ .withTransferOnDate(date) //
+ .build(fromAccountId.toString(), fromClientId.toString(), toAccountId.toString(), toClientId.toString(), fromAccountType,
+ toAccountType, transferAmount);
+ return Utils.performServerPost(this.requestSpec, this.responseSpec, LOAN_REFUND_BY_TRANSFER_URL + "?" + Utils.TENANT_IDENTIFIER,
+ accountTransferJSON, "savingsId");
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
index 4f0b1f7e4..104af8f42 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
@@ -6,12 +6,15 @@
package org.mifosplatform.accounting.journalentry.service;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
import org.mifosplatform.accounting.closure.domain.GLClosure;
import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
import org.mifosplatform.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO;
import org.mifosplatform.accounting.journalentry.data.LoanDTO;
import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO;
import org.mifosplatform.organisation.office.domain.Office;
@@ -72,6 +75,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
.getTransactionType().isWaiveCharges())) {
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true, false);
}
+
+ /** Logic for Refunds of Active Loans **/
+ else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) {
+ createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
+ }
}
}
@@ -339,4 +347,75 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal);
}
}
+ private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
+ // TODO Auto-generated method stub
+ // 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 principalAmount = loanTransactionDTO.getPrincipal();
+ final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+ final BigDecimal feesAmount = loanTransactionDTO.getFees();
+ final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+ final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
+ final boolean isReversal = loanTransactionDTO.isReversed();
+ final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+ BigDecimal totalDebitAmount = new BigDecimal(0);
+
+ if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(principalAmount);
+ this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
+ loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal);
+ }
+
+ if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(interestAmount);
+ this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS,
+ loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal);
+ }
+
+ if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(feesAmount);
+
+ List chargePaymentDTOs = new ArrayList<>();
+
+ for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) {
+ chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(),
+ chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
+ }
+ this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+ CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
+ !isReversal, chargePaymentDTOs);
+ }
+
+ if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+ List chargePaymentDTOs = new ArrayList<>();
+
+ for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) {
+ chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(),
+ chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
+ }
+
+ this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+ CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
+ penaltiesAmount, !isReversal, chargePaymentDTOs);
+ }
+
+ if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+ this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
+ paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal);
+ }
+
+ /*** create a single debit entry (or reversal) for the entire amount **/
+ this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
+ paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal);
+
+ }
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
index ea0bbd18f..aaca832c8 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
@@ -6,11 +6,14 @@
package org.mifosplatform.accounting.journalentry.service;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
import org.mifosplatform.accounting.closure.domain.GLClosure;
import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
import org.mifosplatform.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
+import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO;
import org.mifosplatform.accounting.journalentry.data.LoanDTO;
import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO;
import org.mifosplatform.organisation.office.domain.Office;
@@ -82,6 +85,10 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|| loanTransactionDTO.getTransactionType().isWithdrawTransfer()) {
createJournalEntriesForTransfers(loanDTO, loanTransactionDTO, office);
}
+ /** Logic for Refunds of Active Loans **/
+ else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) {
+ createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
+ }
}
}
@@ -290,4 +297,76 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
null, loanId, transactionId, transactionDate, principalAmount, isReversal);
}
}
+
+ private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
+ // TODO Auto-generated method stub
+ // 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 principalAmount = loanTransactionDTO.getPrincipal();
+ final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+ final BigDecimal feesAmount = loanTransactionDTO.getFees();
+ final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+ final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
+ final boolean isReversal = loanTransactionDTO.isReversed();
+ final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+ BigDecimal totalDebitAmount = new BigDecimal(0);
+
+ if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(principalAmount);
+ this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
+ loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal);
+ }
+
+ if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(interestAmount);
+ this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS,
+ loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal);
+ }
+
+ if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(feesAmount);
+
+ List chargePaymentDTOs = new ArrayList<>();
+
+ for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) {
+ chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(),
+ chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
+ }
+ this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+ CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
+ !isReversal, chargePaymentDTOs);
+ }
+
+ if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+ List chargePaymentDTOs = new ArrayList<>();
+
+ for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) {
+ chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(),
+ chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
+ }
+
+ this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
+ CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
+ penaltiesAmount, !isReversal, chargePaymentDTOs);
+ }
+
+ if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
+ totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+ this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
+ paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal);
+ }
+
+ /*** create a single debit entry (or reversal) for the entire amount **/
+ this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
+ paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal);
+
+ }
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
index 6c2f3aa4b..90d215941 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
@@ -920,5 +920,17 @@ public class CommandWrapper {
public boolean isInactivateSavingsAccountCharge() {
return this.actionName.equalsIgnoreCase("INACTIVATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
}
+
+ public boolean isRefundByTransfer() {
+ return this.actionName.equalsIgnoreCase("REFUNDBYTRANSFER");
+ }
+
+ public boolean isLoanRefundByCash() {
+ return this.actionName.equalsIgnoreCase("REFUNDBYCASH") && this.entityName.equalsIgnoreCase("LOAN");
+ }
+
+ public boolean isUndoLoanRefund() {
+ return this.actionName.equalsIgnoreCase("UNDOREFUND") && this.entityName.equalsIgnoreCase("LOAN");
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
index 9f15a214e..d1468201c 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
@@ -658,6 +658,15 @@ public class CommandWrapperBuilder {
this.href = "/loans/" + loanId + "/transactions/" + transactionId;
return this;
}
+
+ public CommandWrapperBuilder refundLoanTransactionByCash(final Long loanId) {
+ this.actionName = "REFUNDBYCASH";
+ this.entityName = "LOAN";
+ this.entityId = null;
+ this.loanId = loanId;
+ this.href = "/loans/" + loanId + "/transactions/template?command=refundbycash";
+ return this;
+ }
public CommandWrapperBuilder createLoanApplication() {
this.actionName = "CREATE";
@@ -2112,4 +2121,12 @@ public class CommandWrapperBuilder {
this.href = "/rescheduleloans/" + requestId + "?command=reject";
return this;
}
+
+ public CommandWrapperBuilder refundByTransfer() {
+ this.actionName = "REFUNDBYTRANSFER";
+ this.entityName = "ACCOUNTTRANSFER";
+ this.entityId = null;
+ this.href = "/refundByTransfer";
+ return this;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
index bb146845e..ce698a6b0 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
@@ -131,7 +131,11 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
if (wrapper.isAccountTransferResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createAccountTransferCommandHandler", NewCommandSourceHandler.class);
- } else {
+ }
+ else if (wrapper.isRefundByTransfer()) {
+ handler = this.applicationContext.getBean("refundByTransferCommandHandler", NewCommandSourceHandler.class);
+ }
+ else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isConfigurationResource()) {
@@ -389,7 +393,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("loanApplicationDeletionCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUndoLoanWriteOff()) {
handler = this.applicationContext.getBean("undoWriteOffLoanCommandHandler", NewCommandSourceHandler.class);
- } else {
+ }
+ else if (wrapper.isLoanRefundByCash()) {
+ handler = this.applicationContext.getBean("loanRefundByCashCommandHandler", NewCommandSourceHandler.class);
+ }
+ else if (wrapper.isUndoLoanRefund()) {
+ handler = this.applicationContext.getBean("loanRefundAdjustmentCommandHandler", NewCommandSourceHandler.class);
+ }
+ else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isLoanChargeResource()) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/api/AccountTransfersApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/api/AccountTransfersApiResource.java
index c4d3d3d95..a29dc05b0 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/api/AccountTransfersApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/api/AccountTransfersApiResource.java
@@ -119,4 +119,36 @@ public class AccountTransfersApiResource {
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transfer, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
}
+
+ @GET
+ @Path("templateRefundByTransfer")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String templateRefundByTransfer(@QueryParam("fromOfficeId") final Long fromOfficeId, @QueryParam("fromClientId") final Long fromClientId,
+ @QueryParam("fromAccountId") final Long fromAccountId, @QueryParam("fromAccountType") final Integer fromAccountType,
+ @QueryParam("toOfficeId") final Long toOfficeId, @QueryParam("toClientId") final Long toClientId,
+ @QueryParam("toAccountId") final Long toAccountId, @QueryParam("toAccountType") final Integer toAccountType,
+ @Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+ final AccountTransferData transferData = this.accountTransfersReadPlatformService.retrieveRefundByTransferTemplate(fromOfficeId, fromClientId,
+ fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType);
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, transferData, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+ }
+
+ @POST
+ @Path("refundByTransfer")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String templateRefundByTransferPost(final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().refundByTransfer().withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/handler/RefundByTransferCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/handler/RefundByTransferCommandHandler.java
new file mode 100644
index 000000000..720f57a4c
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/handler/RefundByTransferCommandHandler.java
@@ -0,0 +1,32 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.account.handler;
+
+import org.mifosplatform.commands.handler.NewCommandSourceHandler;
+import org.mifosplatform.infrastructure.core.api.JsonCommand;
+import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
+import org.mifosplatform.portfolio.account.service.AccountTransfersWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class RefundByTransferCommandHandler implements NewCommandSourceHandler {
+
+ private final AccountTransfersWritePlatformService writePlatformService;
+
+ @Autowired
+ public RefundByTransferCommandHandler(final AccountTransfersWritePlatformService writePlatformService) {
+ this.writePlatformService = writePlatformService;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+
+ return this.writePlatformService.refundByTransfer(command);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformService.java
index 7c84ecccc..92deb7ecb 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformService.java
@@ -26,4 +26,7 @@ public interface AccountTransfersReadPlatformService {
Page retrieveByStandingInstruction(Long id, SearchParameters searchParameters);
Collection fetchPostInterestTransactionIds(Long accountId);
+
+ AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
+ Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
index c617d6530..776059be6 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
@@ -406,5 +406,114 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
this.accountTransfersMapper);
}
+
+ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
+ final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) {
+ // TODO Auto-generated method stub
+ final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
+ final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
+
+ final Integer mostRelevantFromAccountType = fromAccountType;
+ final Collection fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
+ final Collection toAccountTypeOptions;
+ if (mostRelevantFromAccountType == 1) {
+ // overpaid loan amt transfer to savings account
+ toAccountTypeOptions = Arrays.asList(savingsAccountType);
+ } else {
+ toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
+ }
+ final Integer mostRelevantToAccountType = toAccountType;
+
+ final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType);
+ final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType);
+
+ // from settings
+ OfficeData fromOffice = null;
+ ClientData fromClient = null;
+ PortfolioAccountData fromAccount = null;
+
+ OfficeData toOffice = null;
+ ClientData toClient = null;
+ PortfolioAccountData toAccount = null;
+
+ // template
+ Collection fromAccountOptions = null;
+ Collection toAccountOptions = null;
+
+ Long mostRelevantFromOfficeId = fromOfficeId;
+ Long mostRelevantFromClientId = fromClientId;
+
+ Long mostRelevantToOfficeId = toOfficeId;
+ Long mostRelevantToClientId = toClientId;
+
+ if (fromAccountId != null) {
+ Integer accountType;
+ if (mostRelevantFromAccountType == 1) {
+ accountType = PortfolioAccountType.LOAN.getValue();
+ } else {
+ accountType = PortfolioAccountType.SAVINGS.getValue();
+ }
+ fromAccount = this.portfolioAccountReadPlatformService.retrieveOneByPaidInAdvance(fromAccountId, accountType);
+
+ // override provided fromClient with client of account
+ mostRelevantFromClientId = fromAccount.clientId();
+ }
+
+ if (mostRelevantFromClientId != null) {
+ fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
+ mostRelevantFromOfficeId = fromClient.officeId();
+ long[] loanStatus = null;
+ if (mostRelevantFromAccountType == 1) {
+ loanStatus = new long[] { 300, 700 };
+ }
+ fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(mostRelevantFromAccountType,
+ mostRelevantFromClientId, loanStatus);
+ }
+
+ Collection fromOfficeOptions = null;
+ Collection fromClientOptions = null;
+ if (mostRelevantFromOfficeId != null) {
+ fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
+ fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+ fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
+ }
+
+ // defaults
+ final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
+ Collection toOfficeOptions = fromOfficeOptions;
+ Collection toClientOptions = null;
+
+ if (toAccountId != null && fromAccount != null) {
+ toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType,
+ fromAccount.currencyCode());
+ mostRelevantToClientId = toAccount.clientId();
+ }
+
+ if (mostRelevantToClientId != null) {
+ toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
+ mostRelevantToOfficeId = toClient.officeId();
+
+ toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+
+ toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
+ }
+
+ if (mostRelevantToOfficeId != null) {
+ toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
+ toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+ toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+ if (toClientOptions != null && toClientOptions.size() == 1) {
+ toClient = new ArrayList<>(toClientOptions).get(0);
+
+ toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
+ }
+ }
+
+ return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient,
+ toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
+ toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
+ }
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java
index 68d043e3d..4f2ce4154 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java
@@ -22,4 +22,6 @@ public interface AccountTransfersWritePlatformService {
void reverseAllTransactions(Long accountId, PortfolioAccountType accountTypeId);
void updateLoanTransaction(Long loanTransactionId, LoanTransaction newLoanTransaction);
+
+ CommandProcessingResult refundByTransfer(JsonCommand command);
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
index 17010488f..7cfb59cd9 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
@@ -13,6 +13,7 @@ import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstan
import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName;
import java.math.BigDecimal;
+import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -35,7 +36,10 @@ import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
+import org.mifosplatform.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
+import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException;
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
+import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.portfolio.savings.SavingsTransactionBooleanValues;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
@@ -59,6 +63,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
private final LoanAccountDomainService loanAccountDomainService;
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
private final AccountTransferDetailRepository accountTransferDetailRepository;
+ private final LoanReadPlatformService loanReadPlatformService;
@Autowired
public AccountTransfersWritePlatformServiceImpl(final AccountTransfersDataValidator accountTransfersDataValidator,
@@ -66,7 +71,8 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsAccountAssembler savingsAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService,
final LoanAssembler loanAssembler, final LoanAccountDomainService loanAccountDomainService,
final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
- final AccountTransferDetailRepository accountTransferDetailRepository) {
+ final AccountTransferDetailRepository accountTransferDetailRepository,
+ final LoanReadPlatformService loanReadPlatformService) {
this.accountTransfersDataValidator = accountTransfersDataValidator;
this.accountTransferAssembler = accountTransferAssembler;
this.accountTransferRepository = accountTransferRepository;
@@ -76,6 +82,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
this.loanAccountDomainService = loanAccountDomainService;
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
this.accountTransferDetailRepository = accountTransferDetailRepository;
+ this.loanReadPlatformService = loanReadPlatformService;
}
@Transactional
@@ -385,5 +392,54 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
}
+
+ @Override
+ @Transactional
+ public CommandProcessingResult refundByTransfer(JsonCommand command) {
+ // TODO Auto-generated method stub
+ this.accountTransfersDataValidator.validate(command);
+ final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
+ final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
+
+ final Locale locale = command.extractLocale();
+ final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+
+ final PaymentDetail paymentDetail = null;
+ Long transferTransactionId = null;
+
+ final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
+ final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
+
+ BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(fromLoanAccountId).getPaidInAdvance();
+
+ if (overpaid == null || overpaid.equals(BigDecimal.ZERO) || transactionAmount.floatValue() > overpaid.floatValue()) {
+ if(overpaid == null)
+ overpaid = BigDecimal.ZERO;
+ throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString());
+ }
+
+ final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefundForActiveLoan(fromLoanAccountId,
+ new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
+
+ final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
+ final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId);
+
+ final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate,
+ transactionAmount, paymentDetail, true, true);
+
+ final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command,
+ fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction);
+ this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
+ transferTransactionId = accountTransferDetails.getId();
+
+ final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(transferTransactionId);
+
+ // if (fromAccountType.isSavingsAccount()) {
+
+ builder.withSavingsId(toSavingsAccountId);
+ // }
+
+ return builder.build();
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformService.java
index 9c370b443..c54441046 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformService.java
@@ -18,4 +18,6 @@ public interface PortfolioAccountReadPlatformService {
Collection retrieveAllForLookup(Integer toAccountType, Long toClientId, long[] accountStatus);
Collection retrieveAllForLookup(Integer toAccountType, Long toClientId, String currencyCode, long[] accountStatus, Integer depositType);
+
+ PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId);
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java
index b60f2a513..7eb94f024 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java
@@ -32,12 +32,14 @@ public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccount
// mapper
private final PortfolioSavingsAccountMapper savingsAccountMapper;
private final PortfolioLoanAccountMapper loanAccountMapper;
+ private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
@Autowired
public PortfolioAccountReadPlatformServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
this.loanAccountMapper = new PortfolioLoanAccountMapper();
+ this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper();
}
@Override
@@ -267,4 +269,107 @@ public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccount
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
}
}
+
+ private static final class PortfolioLoanAccountRefundByTransferMapper implements RowMapper {
+
+ private final String schemaSql;
+
+ public PortfolioLoanAccountRefundByTransferMapper() {
+
+ final StringBuilder amountQueryString = new StringBuilder(400);
+ amountQueryString.append("(select (SUM(ifnull(mr.principal_completed_derived, 0)) +");
+ amountQueryString.append("SUM(ifnull(mr.interest_completed_derived, 0)) + ");
+ amountQueryString.append("SUM(ifnull(mr.fee_charges_completed_derived, 0)) + ");
+ amountQueryString.append(" SUM(ifnull(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived");
+ amountQueryString.append(" from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id");
+ amountQueryString.append(" where ml.id=? and ml.loan_status_id = 300");
+ amountQueryString.append(" and mr.duedate >= CURDATE() group by ml.id having");
+ amountQueryString.append(" (SUM(ifnull(mr.principal_completed_derived, 0)) + ");
+ amountQueryString.append(" SUM(ifnull(mr.interest_completed_derived, 0)) + ");
+ amountQueryString.append("SUM(ifnull(mr.fee_charges_completed_derived, 0)) + ");
+ amountQueryString.append("SUM(ifnull(mr.penalty_charges_completed_derived, 0))) > 0) as totalOverpaid ");
+
+ final StringBuilder sqlBuilder = new StringBuilder(400);
+ sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
+ sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
+ sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
+ sqlBuilder.append("lp.id as productId, lp.name as productName, ");
+ sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
+ sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
+ sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
+ sqlBuilder.append(amountQueryString.toString());
+ sqlBuilder.append(", ");
+ sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+ sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
+ sqlBuilder.append("from m_loan la ");
+ sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
+ sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
+ sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
+ sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
+ sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
+
+ this.schemaSql = sqlBuilder.toString();
+ }
+
+ public String schema() {
+ return this.schemaSql;
+ }
+
+ @Override
+ public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+ final Long id = rs.getLong("id");
+ final String accountNo = rs.getString("accountNo");
+ final String externalId = rs.getString("externalId");
+
+ final Long groupId = JdbcSupport.getLong(rs, "groupId");
+ final String groupName = rs.getString("groupName");
+ final Long clientId = JdbcSupport.getLong(rs, "clientId");
+ final String clientName = rs.getString("clientName");
+
+ final Long productId = rs.getLong("productId");
+ final String productName = rs.getString("productName");
+
+ final Long fieldOfficerId = rs.getLong("fieldOfficerId");
+ final String fieldOfficerName = rs.getString("fieldOfficerName");
+
+ final String currencyCode = rs.getString("currencyCode");
+ final String currencyName = rs.getString("currencyName");
+ final String currencyNameCode = rs.getString("currencyNameCode");
+ final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
+ final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+ final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
+ final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
+ final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf,
+ currencyDisplaySymbol, currencyNameCode);
+
+ return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
+ fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
+ }
+ }
+
+ @Override
+ public PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId) {
+ // TODO Auto-generated method stub
+ Object[] sqlParams = new Object[] { accountId , accountId};
+ PortfolioAccountData accountData = null;
+ //String currencyCode = null;
+ try {
+ String sql = null;
+ //final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
+
+ sql = "select " + this.accountRefundByTransferMapper.schema() + " where la.id = ?";
+ /* if (currencyCode != null) {
+ sql += " and la.currency_code = ?";
+ sqlParams = new Object[] {accountId , accountId,currencyCode };
+ }*/
+
+ accountData = this.jdbcTemplate.queryForObject(sql, this.accountRefundByTransferMapper, sqlParams);
+
+ } catch (final EmptyResultDataAccessException e) {
+ throw new AccountTransferNotFoundException(accountId);
+ }
+
+ return accountData;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index ce53e9599..bc9e1bf88 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -96,7 +96,14 @@ public class LoanTransactionsApiResource {
transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId);
} else if (is(commandParam, "prepayLoan")) {
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanId);
- } else {
+ }
+ else if (is(commandParam, "refundbycash")) {
+ transactionData = this.loanReadPlatformService.retrieveRefundByCashTemplate(loanId);
+ }
+ else if (is(commandParam, "refundbytransfer")) {
+ transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
+ }
+ else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
@@ -155,6 +162,10 @@ public class LoanTransactionsApiResource {
final CommandWrapper commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
+ else if (is(commandParam, "refundByCash")) {
+ final CommandWrapper commandRequest = builder.refundLoanTransactionByCash(loanId).build();
+ result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+ }
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java
index 2254fd10b..d5431c8a9 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java
@@ -68,6 +68,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
+import org.mifosplatform.portfolio.loanaccount.data.PaidInAdvanceData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
@@ -426,6 +427,7 @@ public class LoansApiResource {
Collection loanCollateralOptions = null;
Collection calendarOptions = null;
Collection accountLinkingOptions = null;
+ PaidInAdvanceData paidInAdvanceTemplate = null;
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
if (template) {
@@ -470,13 +472,15 @@ public class LoansApiResource {
Collection overdueCharges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanBasicDetails.loanProductId(),
ChargeTimeType.OVERDUE_INSTALLMENT);
+
+ paidInAdvanceTemplate = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId);
final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
- overdueCharges);
+ overdueCharges, paidInAdvanceTemplate);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java
index 43c8ada8c..3e67ca855 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java
@@ -169,6 +169,9 @@ public class LoanAccountData {
private final boolean isInterestRecalculationEnabled;
private final LoanInterestRecalculationData interestRecalculationData;
+
+ //Paid In Advance
+ private final PaidInAdvanceData paidInAdvance;
/**
* Used to produce a {@link LoanAccountData} with only collateral options
@@ -262,6 +265,7 @@ public class LoanAccountData {
final boolean isInterestRecalculationEnabled = false;
final LoanInterestRecalculationData interestRecalculationData = null;
final LoanScheduleData originalSchedule = null;
+ final PaidInAdvanceData paidInAdvance = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -277,7 +281,7 @@ public class LoanAccountData {
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations,
product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
- isInterestRecalculationEnabled, interestRecalculationData, originalSchedule);
+ isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, paidInAdvance);
}
/**
@@ -372,6 +376,7 @@ public class LoanAccountData {
final boolean isInterestRecalculationEnabled = false;
final LoanInterestRecalculationData interestRecalculationData = null;
final LoanScheduleData originalSchedule = null;
+ final PaidInAdvanceData paidInAdvance = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -386,7 +391,7 @@ public class LoanAccountData {
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount,
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
- isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule);
+ isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, paidInAdvance);
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
@@ -409,7 +414,7 @@ public class LoanAccountData {
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
- acc.originalSchedule);
+ acc.originalSchedule, acc.paidInAdvance);
}
/**
@@ -505,6 +510,7 @@ public class LoanAccountData {
final boolean isInterestRecalculationEnabled = false;
final LoanInterestRecalculationData interestRecalculationData = null;
final LoanScheduleData originalSchedule = null;
+ final PaidInAdvanceData paidInAdvance = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -519,7 +525,7 @@ public class LoanAccountData {
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
- daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule);
+ daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, paidInAdvance);
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
@@ -541,7 +547,7 @@ public class LoanAccountData {
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
- acc.originalSchedule);
+ acc.originalSchedule, acc.paidInAdvance);
}
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
@@ -650,6 +656,7 @@ public class LoanAccountData {
final Boolean inArrears = null;
final Boolean isNPA = null;
final LoanScheduleData originalSchedule = null;
+ final PaidInAdvanceData paidInAdvance = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType,
product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId,
@@ -668,7 +675,7 @@ public class LoanAccountData {
product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears,
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(),
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
- originalSchedule);
+ originalSchedule, paidInAdvance);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -694,6 +701,7 @@ public class LoanAccountData {
final Collection loanPurposeOptions = null;
final Collection loanCollateralOptions = null;
final Collection calendarOptions = null;
+ final PaidInAdvanceData paidInAdvance = null;
final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
@@ -722,7 +730,7 @@ public class LoanAccountData {
product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears,
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(),
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
- acc.originalSchedule);
+ acc.originalSchedule, paidInAdvance);
}
/*
@@ -778,6 +786,7 @@ public class LoanAccountData {
final LoanProductData product = null;
final Collection overdueCharges = null;
final LoanScheduleData originalSchedule = null;
+ final PaidInAdvanceData paidInAdvance = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -792,7 +801,7 @@ public class LoanAccountData {
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
- daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule);
+ daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, paidInAdvance);
}
/*
@@ -812,7 +821,8 @@ public class LoanAccountData {
final Collection loanCollateralOptions, final Collection calendarOptions,
final Collection notes, final Collection accountLinkingOptions,
final PortfolioAccountData linkedAccount, final Collection disbursementDetails,
- final Collection emiAmountVariations, final Collection overdueCharges) {
+ final Collection emiAmountVariations, final Collection overdueCharges,
+ final PaidInAdvanceData paidInAdvance) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
@@ -830,7 +840,7 @@ public class LoanAccountData {
accountLinkingOptions, linkedAccount, disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
- acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule);
+ acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, paidInAdvance);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection productOptions,
@@ -841,7 +851,7 @@ public class LoanAccountData {
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions,
- acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges);
+ acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance);
}
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
@@ -862,7 +872,7 @@ public class LoanAccountData {
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
- acc.originalSchedule);
+ acc.originalSchedule, acc.paidInAdvance);
}
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map memberLoanCycle) {
@@ -920,7 +930,7 @@ public class LoanAccountData {
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
- acc.originalSchedule);
+ acc.originalSchedule, acc.paidInAdvance);
}
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
@@ -944,7 +954,7 @@ public class LoanAccountData {
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, interestRecalculationData,
- acc.originalSchedule);
+ acc.originalSchedule, acc.paidInAdvance);
}
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
@@ -966,7 +976,7 @@ public class LoanAccountData {
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
- originalSchedule);
+ originalSchedule, acc.paidInAdvance);
}
private LoanAccountData(
@@ -1022,7 +1032,8 @@ public class LoanAccountData {
final LoanProductData product, final Boolean inArrears, final Integer graceOnArrearsAgeing,
final Collection overdueCharges, final Boolean isNPA, final EnumOptionData daysInMonthType,
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
- final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule) {
+ final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule,
+ final PaidInAdvanceData paidInAdvance) {
this.id = id;
this.accountNo = accountNo;
this.status = status;
@@ -1167,6 +1178,7 @@ public class LoanAccountData {
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
this.interestRecalculationData = interestRecalculationData;
this.originalSchedule = originalSchedule;
+ this.paidInAdvance = paidInAdvance;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java
index 9cd4b074e..5a9fa932e 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java
@@ -30,6 +30,7 @@ public class LoanTransactionEnumData {
private final boolean rejectTransfer;
private final boolean chargePayment;
private final boolean refund;
+ private final boolean refundForActiveLoans;
public LoanTransactionEnumData(final Long id, final String code, final String value) {
this.id = id;
@@ -50,6 +51,7 @@ public class LoanTransactionEnumData {
this.rejectTransfer = Long.valueOf(15).equals(this.id);
this.refund = Long.valueOf(16).equals(this.id);
this.chargePayment = Long.valueOf(17).equals(this.id);
+ this.refundForActiveLoans = Long.valueOf(18).equals(this.id);
}
public Long id() {
@@ -130,5 +132,9 @@ public class LoanTransactionEnumData {
public boolean isRefund() {
return this.refund;
}
+
+ public boolean isRefundForActiveLoans() {
+ return this.refundForActiveLoans;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/PaidInAdvanceData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/PaidInAdvanceData.java
new file mode 100644
index 000000000..eca64d3da
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/PaidInAdvanceData.java
@@ -0,0 +1,23 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.loanaccount.data;
+
+import java.math.BigDecimal;
+
+public class PaidInAdvanceData {
+
+ private final BigDecimal paidInAdvance;
+
+ public PaidInAdvanceData(final BigDecimal paidInAdvance) {
+ this.paidInAdvance = paidInAdvance;
+ }
+
+ public BigDecimal getPaidInAdvance() {
+ return paidInAdvance;
+ }
+
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
index e38db157c..88d12d4f9 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
@@ -82,6 +82,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanR
import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
+import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
@@ -2955,7 +2956,7 @@ public class Loan extends AbstractPersistable {
}
for (final LoanTransaction loanTransaction : this.loanTransactions) {
- if (loanTransaction.isRefund() && !loanTransaction.isReversed()) {
+ if ((loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) && !loanTransaction.isReversed()) {
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
}
}
@@ -3972,6 +3973,11 @@ public class Loan extends AbstractPersistable {
action = "charge.payment";
postfix = "cannot.be.made.before.client.transfer.date";
break;
+ case LOAN_REFUND:
+ errorMessage = "The date on which a refund is made cannot be earlier than client's transfer date to this office";
+ action = "refund";
+ postfix = "cannot.be.made.before.client.transfer.date";
+ break;
default:
break;
}
@@ -4177,6 +4183,15 @@ public class Loan extends AbstractPersistable {
dataValidationErrors.add(error);
}
break;
+ case LOAN_REFUND:
+ if (!isOpen()) {
+ final String defaultUserMessage = "Loan Refund is not allowed. Loan Account is not active.";
+ final ApiParameterError error = ApiParameterError.generalError(
+ "error.msg.loan.refund.account.is.not.active",
+ defaultUserMessage);
+ dataValidationErrors.add(error);
+ }
+ break;
default:
break;
}
@@ -4724,4 +4739,129 @@ public class Loan extends AbstractPersistable {
}
return isEnabled;
}
+
+ public ChangedTransactionDetail makeRefundForActiveLoan(final LoanTransaction loanTransaction,
+ final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds,
+ final List existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List holidays,
+ final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
+
+ validateAccountStatus(LoanEvent.LOAN_REFUND);
+ validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REFUND, loanTransaction.getTransactionDate());
+
+ validateRefundDateIsAfterLastRepayment(loanTransaction.getTransactionDate());
+
+ validateRepaymentDateIsOnHoliday(loanTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
+ validateRepaymentDateIsOnNonWorkingDay(loanTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
+
+ existingTransactionIds.addAll(findExistingTransactionIds());
+ existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
+
+ final ChangedTransactionDetail changedTransactionDetail = handleRefundTransaction(loanTransaction, loanLifecycleStateMachine, null);
+
+ return changedTransactionDetail;
+
+ }
+
+ private void validateRefundDateIsAfterLastRepayment(final LocalDate refundTransactionDate) {
+ // TODO Auto-generated method stub
+ final LocalDate possibleNextRefundDate = possibleNextRefundDate();
+
+ if (possibleNextRefundDate == null || refundTransactionDate.isBefore(possibleNextRefundDate)) { throw new InvalidRefundDateException(
+ refundTransactionDate.toString()); }
+
+ }
+
+ private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction loanTransaction,
+ final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) {
+
+ ChangedTransactionDetail changedTransactionDetail = null;
+
+ final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REFUND, LoanStatus.fromInt(this.loanStatus));
+ this.loanStatus = statusEnum.getValue();
+
+ loanTransaction.updateLoan(this);
+
+ // final boolean isTransactionChronologicallyLatest =
+ // isChronologicallyLatestRefund(loanTransaction,
+ // this.loanTransactions);
+
+ if (status().isOverpaid() || status().isClosed()) {
+
+ final String errorMessage = "This refund option is only for active loans ";
+ throw new InvalidLoanStateTransitionException("transaction", "is.exceeding.overpaid.amount", errorMessage, this.totalOverpaid,
+ loanTransaction.getAmount(getCurrency()).getAmount());
+
+ } else if (this.getTotalPaidInRepayments().isZero()) {
+ final String errorMessage = "Cannot refund when no payment has been made";
+ throw new InvalidLoanStateTransitionException("transaction", "no.payment.yet.made.for.loan", errorMessage);
+ }
+
+ if (loanTransaction.isNotZero(loanCurrency())) {
+ this.loanTransactions.add(loanTransaction);
+ }
+
+ if (loanTransaction.isNotRefundForActiveLoan()) {
+ final String errorMessage = "A transaction of type refund was expected but not received.";
+ throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.refund.transaction", errorMessage);
+ }
+
+ final LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
+ if (loanTransactionDate.isBefore(getDisbursementDate())) {
+ final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
+ + getApprovedOnDate().toString();
+ throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage,
+ loanTransactionDate, getDisbursementDate());
+ }
+
+ if (loanTransactionDate.isAfter(DateUtils.getLocalDateOfTenant())) {
+ final String errorMessage = "The transaction date cannot be in the future.";
+ throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate);
+ }
+
+ if (this.loanProduct.isMultiDisburseLoan() && adjustedTransaction == null) {
+ BigDecimal totalDisbursed = getDisbursedAmount();
+ if (totalDisbursed.compareTo(this.summary.getTotalPrincipalRepaid()) < 0) {
+ final String errorMessage = "The transaction cannot be done before the loan disbursement: "
+ + getApprovedOnDate().toString();
+ throw new InvalidLoanStateTransitionException("transaction", "cannot.be.done.before.disbursement", errorMessage);
+ }
+ }
+
+ final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
+ .determineProcessor(this.transactionProcessingStrategy);
+
+ // If is a refund
+ if (adjustedTransaction == null) {
+ loanRepaymentScheduleTransactionProcessor.handleRefund(loanTransaction, getCurrency(), this.repaymentScheduleInstallments,
+ charges());
+ } else {
+ final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+ allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), null);
+ for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+ mapEntry.getValue().updateLoan(this);
+ }
+
+ }
+
+ updateLoanSummaryDerivedFields();
+
+ doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
+
+ return changedTransactionDetail;
+ }
+
+ public LocalDate possibleNextRefundDate() {
+
+ final LocalDate now = new LocalDate();
+
+ LocalDate lastTransactionDate = null;
+ for (final LoanTransaction transaction : this.loanTransactions) {
+ if ((transaction.isRepayment() || transaction.isRefundForActiveLoan()) && transaction.isNonZero()) {
+ lastTransactionDate = transaction.getTransactionDate();
+ }
+ }
+
+ return lastTransactionDate == null ? now : lastTransactionDate;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java
index c98fbb83f..3bfa7a7e5 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java
@@ -30,6 +30,10 @@ public interface LoanAccountDomainService {
PaymentDetail paymentDetail, String noteText, String txnExternalId);
LocalDate getCalculatedRepaymentsStartingFromDate(LocalDate actualDisbursementDate, Loan loan, CalendarInstance calendarInstance);
+
+ LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult,
+ LocalDate transactionDate, BigDecimal transactionAmount,
+ PaymentDetail paymentDetail, String noteText, String txnExternalId);
/**
* This method is to recalculate and accrue the income till the last accrued
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 8784bd049..cb6acf7d7 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -500,5 +500,46 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.accountTransferRepository.save(transferTransaction);
}
}
+
+ @Override
+ public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult,
+ LocalDate transactionDate, BigDecimal transactionAmount,
+ PaymentDetail paymentDetail, String noteText, String txnExternalId) {
+ // TODO Auto-generated method stub
+ final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
+ checkClientOrGroupActive(loan);
+ final List existingTransactionIds = new ArrayList();
+ final List existingReversedTransactionIds = new ArrayList();
+
+ final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
+ final LoanTransaction newRefundTransaction = LoanTransaction.refundForActiveLoan(loan.getOffice(), refundAmount, paymentDetail, transactionDate,
+ txnExternalId);
+ final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
+ final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
+ transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue());
+ final WorkingDays workingDays = this.workingDaysRepository.findOne();
+ final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
+
+ final ChangedTransactionDetail changedTransactionDetail = loan.makeRefundForActiveLoan(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
+ allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
+
+ this.loanTransactionRepository.save(newRefundTransaction);
+ this.loanRepository.save(loan);
+
+ if (StringUtils.isNotBlank(noteText)) {
+ final Note note = Note.loanTransactionNote(loan, newRefundTransaction, noteText);
+ this.noteRepository.save(note);
+ }
+
+ postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
+ recalculateAccruals(loan);
+
+ builderResult.withEntityId(newRefundTransaction.getId()) //
+ .withOfficeId(loan.getOfficeId()) //
+ .withClientId(loan.getClientId()) //
+ .withGroupId(loan.getGroupId()); //
+
+ return newRefundTransaction;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java
index a1f38f63c..b985f4748 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java
@@ -883,4 +883,48 @@ public class LoanCharge extends AbstractPersistable {
public LoanOverdueInstallmentCharge getOverdueInstallmentCharge() {
return this.overdueInstallmentCharge;
}
+
+ public Money undoPaidOrPartiallyAmountBy(final Money incrementBy,final Integer installmentNumber, final Money feeAmount) {
+ Money processAmount = Money.zero(incrementBy.getCurrency());
+ if(isInstalmentFee()){
+ if(installmentNumber == null){
+ processAmount = getLastPaidOrPartiallyPaidInstallmentLoanCharge(incrementBy.getCurrency()).undoPaidAmountBy(incrementBy, feeAmount);
+ }else{
+ processAmount = getInstallmentLoanCharge(installmentNumber).undoPaidAmountBy(incrementBy, feeAmount);
+ }
+ }else{
+ processAmount = incrementBy;
+ }
+ Money amountPaidToDate = Money.of(processAmount.getCurrency(), this.amountPaid);
+
+ Money amountDeductedOnThisCharge = Money.zero(processAmount.getCurrency());
+ if (processAmount.isGreaterThanOrEqualTo(amountPaidToDate)) {
+ amountDeductedOnThisCharge = amountPaidToDate;
+ amountPaidToDate = Money.zero(processAmount.getCurrency());
+ this.amountPaid = amountPaidToDate.getAmount();
+ this.amountOutstanding = this.amount;
+ this.paid = false;
+
+ } else {
+ amountDeductedOnThisCharge = processAmount;
+ amountPaidToDate = amountPaidToDate.minus(processAmount);
+ this.amountPaid = amountPaidToDate.getAmount();
+ this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
+ }
+ return amountDeductedOnThisCharge;
+ }
+
+ public LoanInstallmentCharge getLastPaidOrPartiallyPaidInstallmentLoanCharge(MonetaryCurrency currency) {
+ LoanInstallmentCharge paidChargePerInstallment = null;
+ for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
+ Money outstanding = Money.of(currency, loanChargePerInstallment.getAmountOutstanding());
+ final boolean partiallyPaid = outstanding.isGreaterThanZero() && outstanding.isLessThan(loanChargePerInstallment.getAmount(currency));
+ if ((partiallyPaid || loanChargePerInstallment.isPaid())
+ && (paidChargePerInstallment == null || paidChargePerInstallment.getRepaymentInstallment().getDueDate()
+ .isBefore(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
+ paidChargePerInstallment = loanChargePerInstallment;
+ }
+ }
+ return paidChargePerInstallment;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java
index dfe6c4237..cf1c92673 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java
@@ -27,5 +27,6 @@ public enum LoanEvent {
LOAN_OVERPAYMENT, //
LOAN_CHARGE_PAYMENT, //
LOAN_CLOSED, //
- LOAN_EDIT_MULTI_DISBURSE_DATE;
+ LOAN_EDIT_MULTI_DISBURSE_DATE, //
+ LOAN_REFUND;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInstallmentCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInstallmentCharge.java
index d0240e9c8..88fd223d1 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInstallmentCharge.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInstallmentCharge.java
@@ -252,5 +252,27 @@ public class LoanInstallmentCharge extends AbstractPersistable {
public void updateInstallment(LoanRepaymentScheduleInstallment installment) {
this.installment = installment;
}
+
+ public Money undoPaidAmountBy(final Money incrementBy, final Money feeAmount) {
+
+ Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
+
+ Money amountToDeductOnThisCharge = Money.zero(incrementBy.getCurrency());
+ if (incrementBy.isGreaterThanOrEqualTo(amountPaidToDate)) {
+ amountToDeductOnThisCharge = amountPaidToDate;
+ amountPaidToDate = Money.zero(incrementBy.getCurrency());
+ this.amountPaid = amountPaidToDate.getAmount();
+ this.amountOutstanding = this.amount;
+ } else {
+ amountToDeductOnThisCharge = incrementBy;
+ amountPaidToDate = amountPaidToDate.minus(incrementBy);
+ this.amountPaid = amountPaidToDate.getAmount();
+ this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
+ }
+ this.amountThroughChargePayment = feeAmount.getAmount();
+ this.paid = determineIfFullyPaid();
+
+ return amountToDeductOnThisCharge;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index f96e4e9b4..27dfe919b 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -563,6 +563,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
if (this.obligationsMet) {
this.obligationsMetOnDate = transactionDate.toDate();
}
+ else {
+ this.obligationsMetOnDate = null;
+ }
}
public void updateDueDate(final LocalDate newDueDate) {
@@ -645,4 +648,113 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return obligationsMetOnDate;
}
+
+ /********** UNPAY COMPONENTS ****/
+
+ public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+ final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+ Money penaltyPortionOfTransactionDeducted = Money.zero(currency);
+
+ final Money penaltyChargesCompleted = getPenaltyChargesPaid(currency);
+ if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesCompleted)) {
+ this.penaltyChargesPaid = Money.zero(currency).getAmount();
+ penaltyPortionOfTransactionDeducted = penaltyChargesCompleted;
+ } else {
+ this.penaltyChargesPaid = penaltyChargesCompleted.minus(transactionAmountRemaining).getAmount();
+ penaltyPortionOfTransactionDeducted = transactionAmountRemaining;
+ }
+
+ checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+ return penaltyPortionOfTransactionDeducted;
+ }
+
+ public Money unpayFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+ final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+ Money feePortionOfTransactionDeducted = Money.zero(currency);
+
+ final Money feeChargesCompleted = getFeeChargesPaid(currency);
+ if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesCompleted)) {
+ this.feeChargesPaid = Money.zero(currency).getAmount();
+ feePortionOfTransactionDeducted = feeChargesCompleted;
+ } else {
+ this.feeChargesPaid = feeChargesCompleted.minus(transactionAmountRemaining).getAmount();
+ feePortionOfTransactionDeducted = transactionAmountRemaining;
+ }
+
+ checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+ reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, feePortionOfTransactionDeducted);
+
+ return feePortionOfTransactionDeducted;
+ }
+
+ public Money unpayInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+ final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+ Money interestPortionOfTransactionDeducted = Money.zero(currency);
+
+ final Money interestCompleted = getInterestPaid(currency);
+ if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestCompleted)) {
+ this.interestPaid = Money.zero(currency).getAmount();
+ interestPortionOfTransactionDeducted = interestCompleted;
+ } else {
+ this.interestPaid = interestCompleted.minus(transactionAmountRemaining).getAmount();
+ interestPortionOfTransactionDeducted = transactionAmountRemaining;
+ }
+
+ checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+ reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, interestPortionOfTransactionDeducted);
+
+ return interestPortionOfTransactionDeducted;
+ }
+
+ public Money unpayPrincipalComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
+
+ final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
+ Money principalPortionOfTransactionDeducted = Money.zero(currency);
+
+ final Money principalCompleted = getPrincipalCompleted(currency);
+ if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalCompleted)) {
+ this.principalCompleted = Money.zero(currency).getAmount();
+ principalPortionOfTransactionDeducted = principalCompleted;
+ } else {
+ this.principalCompleted = principalCompleted.minus(transactionAmountRemaining).getAmount();
+ principalPortionOfTransactionDeducted = transactionAmountRemaining;
+ }
+
+ checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
+
+ reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, principalPortionOfTransactionDeducted);
+
+ return principalPortionOfTransactionDeducted;
+ }
+
+ private void reduceAdvanceAndLateTotalsForRepaymentPeriod(final LocalDate transactionDate, final MonetaryCurrency currency,
+ final Money amountDeductedInRepaymentPeriod) {
+
+
+ if (isInAdvance(transactionDate)) {
+ Money mTotalPaidInAdvance = Money.of(currency,this.totalPaidInAdvance);
+
+ if(mTotalPaidInAdvance.isLessThan(amountDeductedInRepaymentPeriod) || mTotalPaidInAdvance.isEqualTo(amountDeductedInRepaymentPeriod))
+ this.totalPaidInAdvance = Money.zero(currency).getAmount();
+ else
+ this.totalPaidInAdvance = mTotalPaidInAdvance.minus(amountDeductedInRepaymentPeriod).getAmount();
+ } else if (isLatePayment(transactionDate)) {
+ Money mTotalPaidLate = Money.of(currency,this.totalPaidLate);
+
+ if(mTotalPaidLate.isLessThan(amountDeductedInRepaymentPeriod) || mTotalPaidLate.isEqualTo(amountDeductedInRepaymentPeriod))
+ this.totalPaidLate = Money.zero(currency).getAmount();
+ else
+ this.totalPaidLate = mTotalPaidLate.minus(amountDeductedInRepaymentPeriod).getAmount();
+ }
+ }
+
+ public Money getDue(MonetaryCurrency currency) {
+ return getPrincipal(currency).plus(getInterestCharged(currency)).plus(getFeeChargesCharged(currency)).plus(getPenaltyChargesCharged(currency));
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java
index 0b5d2bbc0..c692f8d46 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java
@@ -204,6 +204,11 @@ public final class LoanTransaction extends AbstractPersistable {
applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
return applyCharge;
}
+
+ public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail,
+ final LocalDate paymentDate, final String externalId) {
+ return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(), paymentDate, externalId);
+ }
public static boolean transactionAmountsMatch(final MonetaryCurrency currency, final LoanTransaction loanTransaction,
final LoanTransaction newLoanTransaction) {
@@ -577,5 +582,14 @@ public final class LoanTransaction extends AbstractPersistable {
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
this.outstandingLoanBalance = outstandingLoanBalance;
}
+
+ public boolean isNotRefundForActiveLoan() {
+ // TODO Auto-generated method stub
+ return !isRefundForActiveLoan();
+ }
+
+ public boolean isRefundForActiveLoan() {
+ return LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.equals(getTypeOf()) && isNotReversed();
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java
index 5337d056b..632e83038 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -37,7 +37,8 @@ public enum LoanTransactionType {
WITHDRAW_TRANSFER(14, "loanTransactionType.withdrawTransfer"), //
REJECT_TRANSFER(15, "loanTransactionType.rejectTransfer"), //
REFUND(16, "loanTransactionType.refund"), //
- CHARGE_PAYMENT(17, "loanTransactionType.chargePayment");
+ CHARGE_PAYMENT(17, "loanTransactionType.chargePayment"), //
+ REFUND_FOR_ACTIVE_LOAN(18, "loanTransactionType.refund");
private final Integer value;
private final String code;
@@ -109,6 +110,9 @@ public enum LoanTransactionType {
case 17:
loanTransactionType = LoanTransactionType.CHARGE_PAYMENT;
break;
+ case 18:
+ loanTransactionType = LoanTransactionType.REFUND_FOR_ACTIVE_LOAN;
+ break;
default:
loanTransactionType = LoanTransactionType.INVALID;
break;
@@ -151,4 +155,8 @@ public enum LoanTransactionType {
public boolean isChargePayment() {
return this.value.equals(LoanTransactionType.CHARGE_PAYMENT.getValue());
}
+
+ public boolean isRefundForActiveLoan() {
+ return this.value.equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue());
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 9a90a6743..e538c674e 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -5,7 +5,10 @@
*/
package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor;
+import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -22,6 +25,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
+import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
@@ -136,6 +140,16 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
+
+ if(!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) {
+ final Comparator byDate = new Comparator() {
+ @Override
+ public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) {
+ return ord1.getDueDate().compareTo(ord2.getDueDate());
+ }
+ };
+ Collections.sort(installments,byDate);
+ }
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
// pass through for new transactions
@@ -170,6 +184,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
loanTransaction.resetDerivedComponents();
handleWriteOff(loanTransaction, currency, installments);
}
+ else if(loanTransaction.isRefundForActiveLoan()) {
+ loanTransaction.resetDerivedComponents();
+
+ handleRefund(loanTransaction, currency, installments,charges);
+ }
}
return changedTransactionDetail;
}
@@ -510,6 +529,104 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
}
+
+ @Override
+ public void handleRefund(LoanTransaction loanTransaction,
+ MonetaryCurrency currency,
+ List installments,
+ final Set charges) {
+ // TODO Auto-generated method stub
+
+ final Comparator byDate = new Comparator() {
+ @Override
+ public int compare(LoanRepaymentScheduleInstallment ord1,
+ LoanRepaymentScheduleInstallment ord2) {
+ return ord1.getDueDate().compareTo(ord2.getDueDate());
+ }
+ };
+ Collections.sort(installments, Collections.reverseOrder(byDate));
+ Money transactionAmountUnprocessed = loanTransaction
+ .getAmount(currency);
+
+ for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
+ Money outstanding = currentInstallment
+ .getTotalOutstanding(currency);
+ Money due = currentInstallment.getDue(currency);
+
+ if (outstanding.isLessThan(due)) {
+ transactionAmountUnprocessed = handleRefundTransactionPaymentOfInstallment(
+ currentInstallment, loanTransaction,
+ transactionAmountUnprocessed);
+
+ }
+
+ if (transactionAmountUnprocessed.isZero())
+ break;
+
+ }
+
+ final Set loanFees = extractFeeCharges(charges);
+ final Set loanPenalties = extractPenaltyCharges(charges);
+ Integer installmentNumber = null;
+
+ final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
+ if (feeCharges.isGreaterThanZero()) {
+ undoChargesPaidAmountBy(loanTransaction, feeCharges, loanFees,
+ installmentNumber);
+ }
+
+ final Money penaltyCharges = loanTransaction
+ .getPenaltyChargesPortion(currency);
+ if (penaltyCharges.isGreaterThanZero()) {
+ undoChargesPaidAmountBy(loanTransaction, penaltyCharges,
+ loanPenalties, installmentNumber);
+ }
+
+ }
+
+ /**
+ * Invoked when a there is a refund of an active loan or undo of an active
+ * loan
+ *
+ * Undoes principal, interest, fees and charges of this transaction based on
+ * the repayment strategy
+ *
+ */
+ protected abstract Money handleRefundTransactionPaymentOfInstallment(
+ final LoanRepaymentScheduleInstallment currentInstallment,
+ final LoanTransaction loanTransaction,
+ final Money transactionAmountUnprocessed);
+
+ private void undoChargesPaidAmountBy(final LoanTransaction loanTransaction,
+ final Money feeCharges, final Set charges,
+ final Integer installmentNumber) {
+
+ Money amountRemaining = feeCharges;
+ while (amountRemaining.isGreaterThanZero()) {
+ final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet(
+ charges, feeCharges.getCurrency());
+
+ if (paidCharge != null) {
+ Money feeAmount = feeCharges.zero();
+
+ final Money amountDeductedTowardsCharge = paidCharge
+ .undoPaidOrPartiallyAmountBy(amountRemaining,
+ installmentNumber, feeAmount);
+ if (amountDeductedTowardsCharge.isGreaterThanZero()) {
+
+ final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(
+ loanTransaction, paidCharge,
+ amountDeductedTowardsCharge.getAmount().multiply(
+ new BigDecimal(-1)), null);
+ loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
+
+ amountRemaining = amountRemaining
+ .minus(amountDeductedTowardsCharge);
+ }
+ }
+ }
+
+ }
@Override
public ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(final LocalDate disbursementDate,
@@ -519,5 +636,48 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom,
reprocessCharges);
}
+
+ private LoanCharge findLatestPaidChargeFromUnOrderedSet(
+ final Set charges, MonetaryCurrency currency) {
+ LoanCharge latestPaidCharge = null;
+ LoanCharge installemntCharge = null;
+ LoanInstallmentCharge chargePerInstallment = null;
+ for (final LoanCharge loanCharge : charges) {
+ boolean isPaidOrPartiallyPaid = loanCharge
+ .isPaidOrPartiallyPaid(currency);
+ if (isPaidOrPartiallyPaid && !loanCharge.isDueAtDisbursement()) {
+ if (loanCharge.isInstalmentFee()) {
+ LoanInstallmentCharge paidLoanChargePerInstallment = loanCharge
+ .getLastPaidOrPartiallyPaidInstallmentLoanCharge(currency);
+ if (chargePerInstallment == null
+ || (paidLoanChargePerInstallment != null && chargePerInstallment
+ .getRepaymentInstallment()
+ .getDueDate()
+ .isBefore(
+ paidLoanChargePerInstallment
+ .getRepaymentInstallment()
+ .getDueDate()))) {
+ installemntCharge = loanCharge;
+ chargePerInstallment = paidLoanChargePerInstallment;
+ }
+ } else if (latestPaidCharge == null
+ || (loanCharge.isPaidOrPartiallyPaid(currency))
+ && loanCharge.getDueLocalDate().isAfter(
+ latestPaidCharge.getDueLocalDate())) {
+ latestPaidCharge = loanCharge;
+ }
+ }
+ }
+ if (latestPaidCharge == null
+ || (chargePerInstallment != null && latestPaidCharge
+ .getDueLocalDate().isAfter(
+ chargePerInstallment.getRepaymentInstallment()
+ .getDueDate()))) {
+ latestPaidCharge = installemntCharge;
+ }
+
+ return latestPaidCharge;
+ }
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
index 234295414..6280133fa 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
@@ -45,5 +45,10 @@ public interface LoanRepaymentScheduleTransactionProcessor {
ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(LocalDate disbursementDate,
List transactionsPostDisbursement, MonetaryCurrency currency,
List installments, Set charges, LocalDate recalculateChargesFrom);
+
+ void handleRefund(LoanTransaction loanTransaction,
+ MonetaryCurrency currency,
+ List installments,
+ final Set charges);
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java
index f974adc5f..592218e1f 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java
@@ -129,4 +129,37 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// dont do anything for with loan over-payment
}
+
+ @Override
+ protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+ final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
+
+ final LocalDate transactionDate = loanTransaction.getTransactionDate();
+ Money transactionAmountRemaining = transactionAmountUnprocessed;
+ Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+ principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+ if(transactionAmountRemaining.isGreaterThanZero()) {
+ interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+ }
+
+ if(transactionAmountRemaining.isGreaterThanZero()) {
+ feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+ }
+
+ if(transactionAmountRemaining.isGreaterThanZero()) {
+ penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+ }
+ loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+
+ return transactionAmountRemaining;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java
index 41a22d847..45f8b73e0 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java
@@ -137,5 +137,42 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr
return transactionAmountRemaining;
}
+
+ @Override
+ protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+ final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
+
+ final LocalDate transactionDate = loanTransaction.getTransactionDate();
+ final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+ Money transactionAmountRemaining = transactionAmountUnprocessed;
+ Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+ }
+
+ loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+
+ return transactionAmountRemaining;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java
index 68662c0d8..c769378d1 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java
@@ -181,4 +181,41 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {}
+
+ @Override
+ protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+ final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
+
+ final LocalDate transactionDate = loanTransaction.getTransactionDate();
+ final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+ Money transactionAmountRemaining = transactionAmountUnprocessed;
+ Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+ }
+
+ loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+
+ return transactionAmountRemaining;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
index 431f33cc0..6f02d69fc 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
@@ -104,4 +104,40 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
return transactionAmountRemaining;
}
+
+ @Override
+ protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+ final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
+
+ final LocalDate transactionDate = loanTransaction.getTransactionDate();
+ Money transactionAmountRemaining = transactionAmountUnprocessed;
+ Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+ }
+
+ loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+
+ return transactionAmountRemaining;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java
index f574654ac..ddb9c7347 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java
@@ -123,4 +123,38 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends Abstrac
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now
}
+
+ @Override
+ protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+ final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
+
+ final LocalDate transactionDate = loanTransaction.getTransactionDate();
+ final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+ Money transactionAmountRemaining = transactionAmountUnprocessed;
+ Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+ principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+ }
+ loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+
+ return transactionAmountRemaining;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
index e2d692a31..531f81f29 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
@@ -104,4 +104,42 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
return transactionAmountRemaining;
}
+
+ @Override
+ protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+ final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
+
+ final LocalDate transactionDate = loanTransaction.getTransactionDate();
+ final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+ Money transactionAmountRemaining = transactionAmountUnprocessed;
+ Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+ }
+
+ loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+
+ return transactionAmountRemaining;
+ }
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java
index b98e6eefb..95616049e 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java
@@ -209,4 +209,41 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe
public boolean isInterestFirstRepaymentScheduleTransactionProcessor() {
return true;
}
+
+ @Override
+ protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
+ final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
+
+ final LocalDate transactionDate = loanTransaction.getTransactionDate();
+ final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
+ Money transactionAmountRemaining = transactionAmountUnprocessed;
+ Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+ Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
+ }
+
+ if (transactionAmountRemaining.isGreaterThanZero()) {
+ penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
+ transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
+ }
+
+ loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
+
+ return transactionAmountRemaining;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/InvalidPaidInAdvanceAmountException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/InvalidPaidInAdvanceAmountException.java
new file mode 100644
index 000000000..263d55fd4
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/InvalidPaidInAdvanceAmountException.java
@@ -0,0 +1,21 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.loanaccount.exception;
+
+import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class InvalidPaidInAdvanceAmountException extends AbstractPlatformDomainRuleException {
+
+ public InvalidPaidInAdvanceAmountException(final String refundAmountString) {
+ super("error.msg.loan.refund.amount.invalid", "The refund amount `" + refundAmountString + "`"
+ + "` is invalid or loan is not paid in advance.", new Object[] { refundAmountString});
+ }
+
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/InvalidRefundDateException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/InvalidRefundDateException.java
new file mode 100644
index 000000000..53a01edc4
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/InvalidRefundDateException.java
@@ -0,0 +1,20 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.loanaccount.exception;
+
+import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class InvalidRefundDateException extends AbstractPlatformDomainRuleException {
+
+ public InvalidRefundDateException(final String refundDateAsString) {
+ super("error.msg.loan.refund.failed", "The refund date`" + refundDateAsString + "`"
+ + "` cannot be before the smallest repayment transaction date", new Object[] { refundDateAsString});
+ }
+
+ public InvalidRefundDateException(final String defaultUserMessage,final String entity,final Object... defaultUserMessageArgs) {
+ super("error.msg.loan." + entity , defaultUserMessage, defaultUserMessageArgs);
+ }
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanRefundByCashCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanRefundByCashCommandHandler.java
new file mode 100644
index 000000000..6f13457ae
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanRefundByCashCommandHandler.java
@@ -0,0 +1,32 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.loanaccount.handler;
+
+import org.mifosplatform.commands.handler.NewCommandSourceHandler;
+import org.mifosplatform.infrastructure.core.api.JsonCommand;
+import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
+import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class LoanRefundByCashCommandHandler implements NewCommandSourceHandler {
+
+ private final LoanWritePlatformService writePlatformService;
+
+ @Autowired
+ public LoanRefundByCashCommandHandler(final LoanWritePlatformService writePlatformService) {
+ this.writePlatformService = writePlatformService;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+
+ return this.writePlatformService.makeLoanRefund(command.getLoanId(), command);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
index c0d595446..67cac314f 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
@@ -388,5 +388,33 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
+
+ public void validateNewRefundTransaction(final String json) {
+
+ if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+ final Set transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "externalId",
+ "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber",
+ "bankNumber"));
+
+ final Type typeOfMap = new TypeToken