diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 84b5e04dd..f81d9a7cc 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -341,6 +341,22 @@ + + + accounttransfers/templateRefundByTransfer + + Retrieve Refund of an Active Loan by Transfer Template + + + + + + accounttransfers/refundByTransfer + Refund an active loan by transfer + + + + @@ -1022,6 +1038,14 @@ + + loans/{loanId}/transactions?command=refundByCash + Refund an Active Loan by Cash + + + + + Loan Charges loans/{loanId}/charges Add a Loan Charge @@ -8538,6 +8562,11 @@ No Request Body: outstanding. The amount that can be waived is limited to the loan's inArrearsTolerance +
+ 'refundbycash'
"date" is set to the current date
+ "total" "amount" is set + to the total amount paid in advance. +

Example Request:

loans/1/transactions/template?command=repayment
@@ -8624,6 +8653,45 @@ Request Body: + +   +
+
+

Make a Refund of an Active Loan by Cash

+
+
+ +POST https://DomainName/api/v1/loans/{loanId}/transactions?command=refundByCash + + +POST loans/5/transactions?command=refundByCash +Content-Type: application/json +Request Body: +{ + "dateFormat": "dd MMMM yyyy", + "locale": "en", + "transactionDate": "14 May 2013", + "transactionAmount": "500.00", + "paymentTypeId": "12" +} + + +{ + "officeId": 1, + "clientId": 1, + "loanId": 5, + "resourceId": 564, + "changes": { + "transactionDate": "14 May 2013", + "transactionAmount": "500.00", + "locale": "en", + "dateFormat": "dd MMMM yyyy + } +} + +
+
+  
@@ -8882,7 +8950,7 @@ Request Body:
- +   @@ -24780,6 +24848,285 @@ No Request Body: "id": 1, "accountNo": "000000001" } +} + + + + +   +
+
+

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>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element); + baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull(); + + final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element); + baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount(); + + final String note = this.fromApiJsonHelper.extractStringNamed("note", element); + baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000); + + validatePaymentDetails(baseDataValidator, element); + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java index 31a70dcb7..0c60c8c6c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -6,6 +6,7 @@ package org.mifosplatform.portfolio.loanaccount.service; import java.util.Collection; +import java.util.Date; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.service.Page; @@ -17,8 +18,8 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData; import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData; 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.LoanTransactionType; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData; @@ -97,4 +98,10 @@ public interface LoanReadPlatformService { Collection retrieveWaiverLoanTransactions(Long loanId); Collection fetchWaiverInterestRepaymentData(Long loanId); + + Date retrieveMinimumDateOfRepaymentTransaction(Long loanId); + + PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId); + + LoanTransactionData retrieveRefundByCashTemplate(Long loanId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index d70b961f3..034f10953 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -67,6 +68,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanSummaryData; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; +import org.mifosplatform.portfolio.loanaccount.data.PaidInAdvanceData; import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -1744,5 +1746,60 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } } + + @Override + public Date retrieveMinimumDateOfRepaymentTransaction(Long loanId) { + // TODO Auto-generated method stub + Date date = this.jdbcTemplate.queryForObject( + "select min(transaction_date) from m_loan_transaction where loan_id=? and transaction_type_enum=2", + new Object[] { loanId }, Date.class); + + return date; + } + + @Override + public PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId) { + // TODO Auto-generated method stub + try { + final String sql = " select (SUM(ifnull(mr.principal_completed_derived, 0)) +" + + " + SUM(ifnull(mr.interest_completed_derived, 0)) " + " + SUM(ifnull(mr.fee_charges_completed_derived, 0)) " + + " + SUM(ifnull(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived " + + " from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id " + + " where ml.id=? and mr.duedate >= CURDATE() group by ml.id having " + + " (SUM(ifnull(mr.principal_completed_derived, 0)) " + " + SUM(ifnull(mr.interest_completed_derived, 0)) " + + " + SUM(ifnull(mr.fee_charges_completed_derived, 0)) " + + "+ SUM(ifnull(mr.penalty_charges_completed_derived, 0))) > 0"; + BigDecimal bigDecimal = this.jdbcTemplate.queryForObject(sql, BigDecimal.class, loanId); + return new PaidInAdvanceData(bigDecimal); + } catch (DataAccessException e) { + // TODO Auto-generated catch block + return new PaidInAdvanceData(new BigDecimal(0)); + } + } + + @Override + public LoanTransactionData retrieveRefundByCashTemplate(Long loanId) { + // TODO Auto-generated method stub + this.context.authenticatedUser(); + + // TODO - KW - OPTIMIZE - write simple sql query to fetch back date of + // possible next transaction date. + final Loan loan = this.loanRepository.findOne(loanId); + if (loan == null) { throw new LoanNotFoundException(loanId); } + + final MonetaryCurrency currency = loan.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + + final CurrencyData currencyData = applicationCurrency.toData(); + + final LocalDate earliestUnpaidInstallmentDate = new LocalDate(); + + final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN); + final Collection paymentOptions = this.codeValueReadPlatformService + .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); + return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, + retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null, null, + null, null); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java index 93e3151dd..23b48aefa 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -81,4 +81,6 @@ public interface LoanWritePlatformService { void recalculateInterest(); + CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 2d51fa75b..55f042342 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -9,6 +9,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -109,6 +110,8 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; 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.exception.LoanDisbursalException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException; @@ -2557,5 +2560,54 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } return installments; } + + @Override + @Transactional + public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) { + // TODO Auto-generated method stub + + this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json()); + + final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); + + //checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate); + + final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + checkIfLoanIsPaidInAdvance(loanId, transactionAmount); + + final Map changes = new LinkedHashMap(); + changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); + changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount")); + changes.put("locale", command.locale()); + changes.put("dateFormat", command.dateFormat()); + + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + changes.put("note", noteText); + } + + final PaymentDetail paymentDetail = null; + + final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); + + this.loanAccountDomainService.makeRefundForActiveLoan(loanId, commandProcessingResultBuilder, transactionDate, transactionAmount, + paymentDetail, noteText, null); + + return commandProcessingResultBuilder.withCommandId(command.commandId()) // + .withLoanId(loanId) // + .with(changes) // + .build(); + + } + + private void checkIfLoanIsPaidInAdvance(final Long loanId,final BigDecimal transactionAmount) { + BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId).getPaidInAdvance(); + + if(overpaid == null || overpaid.equals(new BigDecimal(0)) || transactionAmount.floatValue() > overpaid.floatValue()) { + if(overpaid == null) + overpaid = BigDecimal.ZERO; + throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString()); + } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index 6e3d2fd05..3f523d72c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -319,6 +319,10 @@ public class LoanEnumerations { optionData = new LoanTransactionEnumData(LoanTransactionType.CHARGE_PAYMENT.getValue().longValue(), LoanTransactionType.CHARGE_PAYMENT.getCode(), "Charge Payment"); break; + case REFUND_FOR_ACTIVE_LOAN: + optionData = new LoanTransactionEnumData(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue().longValue(), + LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getCode(), "Refund"); + break; default: break; } diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V207__loan-refund-permissions.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V207__loan-refund-permissions.sql new file mode 100755 index 000000000..174d1a366 --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V207__loan-refund-permissions.sql @@ -0,0 +1,16 @@ +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES + ('transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 0); + + INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES + ( 'transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 1); + + INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES + ('transaction_loan', 'REFUNDBYCASH_LOAN', 'LOAN', 'REFUNDBYCASH', 1); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES + ('transaction_loan', 'REFUNDBYCASH_LOAN_CHECKER', 'LOAN', 'REFUNDBYCASH', 0); +