diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index 590a876e3..4539ec44c 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -1418,7 +1418,14 @@
diff --git a/mifosng-db/migrations/core_db/V187__added_permission_to_periodic_accrual.sql b/mifosng-db/migrations/core_db/V187__added_permission_to_periodic_accrual.sql
new file mode 100755
index 000000000..c2233a674
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V187__added_permission_to_periodic_accrual.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('accounting', 'EXECUTE_PERIODICACCRUALACCOUNTING', 'PERIODICACCRUALACCOUNTING', 'EXECUTE', 0);
\ No newline at end of file
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java
index 8230c9fd2..1a8bfbf64 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java
@@ -22,6 +22,7 @@ import org.mifosplatform.integrationtests.common.accounting.Account;
import org.mifosplatform.integrationtests.common.accounting.AccountHelper;
import org.mifosplatform.integrationtests.common.accounting.JournalEntry;
import org.mifosplatform.integrationtests.common.accounting.JournalEntryHelper;
+import org.mifosplatform.integrationtests.common.accounting.PeriodicAccrualAccountingHelper;
import org.mifosplatform.integrationtests.common.charges.ChargesHelper;
import org.mifosplatform.integrationtests.common.fixeddeposit.FixedDepositAccountHelper;
import org.mifosplatform.integrationtests.common.fixeddeposit.FixedDepositAccountStatusChecker;
@@ -84,6 +85,7 @@ public class AccountingScenarioIntegrationTest {
private RecurringDepositProductHelper recurringDepositProductHelper;
private RecurringDepositAccountHelper recurringDepositAccountHelper;
private SchedulerJobHelper schedulerJobHelper;
+ private PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper;
@Before
public void setup() {
@@ -96,6 +98,7 @@ public class AccountingScenarioIntegrationTest {
this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
+ this.periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(this.requestSpec, this.responseSpec);
}
@Test
@@ -722,6 +725,80 @@ public class AccountingScenarioIntegrationTest {
}
+ @Test
+ public void checkPeriodicAccrualAccountingAPIFlow() {
+ 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 = createLoanProductWithPeriodicAccrualAccountingEnabled(assetAccount, incomeAccount, expenseAccount,
+ overpaymentAccount);
+
+ final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING);
+ final Integer loanID = applyForLoanApplication(clientID, loanProductID);
+
+ final float FEE_PORTION = 50.0f;
+ final float PENALTY_PORTION = 100.0f;
+ Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
+ ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(FEE_PORTION), false));
+ Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON(
+ ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(PENALTY_PORTION), true));
+
+ HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ loanStatusHashMap = this.loanTransactionHelper.approveLoan(this.EXPECTED_DISBURSAL_DATE, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
+
+ Calendar todayDate = Calendar.getInstance();
+ final String currentDate = dateFormat.format(todayDate.getTime());
+
+ todayDate.add(Calendar.DATE, -4);
+
+ final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todayDate.getTime());
+
+ todayDate.add(Calendar.MONTH, 2);
+ final String FIRST_REPAYMENT_DATE = dateFormat.format(todayDate.getTime());
+
+ todayDate = Calendar.getInstance();
+ todayDate.add(Calendar.DATE, -2);
+
+ loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ this.loanTransactionHelper.addChargesForLoan(
+ loanID,
+ LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDate),
+ dateFormat.format(todayDate.getTime()), String.valueOf(PENALTY_PORTION)));
+ todayDate.add(Calendar.DATE, 1);
+ String runOndate = dateFormat.format(todayDate.getTime());
+ this.loanTransactionHelper
+ .addChargesForLoan(
+ loanID,
+ LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flat), runOndate,
+ String.valueOf(FEE_PORTION)));
+
+ // CHECK ACCOUNT ENTRIES
+ System.out.println("Entries ......");
+ final float PRINCIPAL_VALUE_FOR_EACH_PERIOD = 2000.0f;
+ final float TOTAL_INTEREST = 1000.0f;
+ final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.CREDIT),
+ new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.DEBIT), };
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, LOAN_DISBURSEMENT_DATE, assetAccountInitialEntry);
+
+ this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOndate);
+
+ // MAKE 1
+ final float INTEREST_3_DAYS = 10.0F;
+ this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(runOndate), INTEREST_3_DAYS, FEE_PORTION,
+ PENALTY_PORTION, loanID);
+
+ }
+
private Integer createLoanProductWithPeriodicAccrualAccountingEnabled(final Account... accounts) {
System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(this.LP_PRINCIPAL.toString()).withRepaymentTypeAsMonth()
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/accounting/PeriodicAccrualAccountingHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/accounting/PeriodicAccrualAccountingHelper.java
new file mode 100755
index 000000000..4d5dc0e10
--- /dev/null
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/accounting/PeriodicAccrualAccountingHelper.java
@@ -0,0 +1,35 @@
+package org.mifosplatform.integrationtests.common.accounting;
+
+import java.util.HashMap;
+
+import org.mifosplatform.integrationtests.common.Utils;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class PeriodicAccrualAccountingHelper {
+
+ private static final String PERIODIC_ACCRUAL_URL = "/mifosng-provider/api/v1/accrualaccounting";
+ private final RequestSpecification requestSpec;
+ private final ResponseSpecification responseSpec;
+
+ public PeriodicAccrualAccountingHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+ this.requestSpec = requestSpec;
+ this.responseSpec = responseSpec;
+ }
+
+ public Object runPeriodicAccrualAccounting(String date) {
+ String json = getRunPeriodicAccrual(date);
+ return Utils.performServerPost(this.requestSpec, this.responseSpec, PERIODIC_ACCRUAL_URL + "?" + Utils.TENANT_IDENTIFIER, json, "");
+ }
+
+ private String getRunPeriodicAccrual(String date) {
+ final HashMap
map = new HashMap<>();
+ map.put("dateFormat", "dd MMMM yyyy");
+ map.put("locale", "en_GB");
+ map.put("accrueTill", date);
+ return new Gson().toJson(map);
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/api/AccrualAccountingApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/api/AccrualAccountingApiResource.java
new file mode 100755
index 000000000..e7e558b2c
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/api/AccrualAccountingApiResource.java
@@ -0,0 +1,51 @@
+/**
+ * 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.accounting.accrual.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.mifosplatform.commands.domain.CommandWrapper;
+import org.mifosplatform.commands.service.CommandWrapperBuilder;
+import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
+import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/accrualaccounting")
+@Component
+@Scope("singleton")
+public class AccrualAccountingApiResource {
+
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ private final DefaultToApiJsonSerializer apiJsonSerializerService;
+
+ @Autowired
+ public AccrualAccountingApiResource(final DefaultToApiJsonSerializer apiJsonSerializerService,
+ final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ this.apiJsonSerializerService = apiJsonSerializerService;
+
+ }
+
+ @POST
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String executePeriodicAccrualAccounting(final String jsonRequestBody) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().excuteAccrualAccounting().withJson(jsonRequestBody).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.apiJsonSerializerService.serialize(result);
+ }
+
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/api/AccrualAccountingConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/api/AccrualAccountingConstants.java
new file mode 100755
index 000000000..88a892f8e
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/api/AccrualAccountingConstants.java
@@ -0,0 +1,18 @@
+package org.mifosplatform.accounting.accrual.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AccrualAccountingConstants {
+
+ public static final String accrueTillParamName = "accrueTill";
+ public static final String localeParamName = "locale";
+ public static final String dateFormatParamName = "dateFormat";
+
+ public static final String PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME = "periodicaccrual";
+ public static final String PERIODIC_ACCRUAL_ACCOUNTING_EXECUTION_ERROR_CODE = "execution.failed";
+
+ public static final Set LOAN_PERIODIC_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(accrueTillParamName,
+ localeParamName, dateFormatParamName));
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/handler/ExecutePeriodicAccrualCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/handler/ExecutePeriodicAccrualCommandHandler.java
new file mode 100755
index 000000000..c2e41ce3a
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/handler/ExecutePeriodicAccrualCommandHandler.java
@@ -0,0 +1,31 @@
+/**
+ * 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.accounting.accrual.handler;
+
+import org.mifosplatform.accounting.accrual.service.AccrualAccountingWritePlatformService;
+import org.mifosplatform.commands.handler.NewCommandSourceHandler;
+import org.mifosplatform.infrastructure.core.api.JsonCommand;
+import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class ExecutePeriodicAccrualCommandHandler implements NewCommandSourceHandler {
+
+ private final AccrualAccountingWritePlatformService writePlatformService;
+
+ @Autowired
+ public ExecutePeriodicAccrualCommandHandler(final AccrualAccountingWritePlatformService writePlatformService) {
+ this.writePlatformService = writePlatformService;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.executeLoansPeriodicAccrual(command);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/serialization/AccrualAccountingDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/serialization/AccrualAccountingDataValidator.java
new file mode 100755
index 000000000..70429540c
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/accrual/serialization/AccrualAccountingDataValidator.java
@@ -0,0 +1,66 @@
+/**
+ * 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.accounting.accrual.serialization;
+
+import static org.mifosplatform.accounting.accrual.api.AccrualAccountingConstants.LOAN_PERIODIC_REQUEST_DATA_PARAMETERS;
+import static org.mifosplatform.accounting.accrual.api.AccrualAccountingConstants.PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME;
+import static org.mifosplatform.accounting.accrual.api.AccrualAccountingConstants.accrueTillParamName;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.joda.time.LocalDate;
+import org.mifosplatform.infrastructure.core.data.ApiParameterError;
+import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
+import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
+import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer;
+import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
+import org.mifosplatform.portfolio.loanaccount.guarantor.command.GuarantorCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Implementation of {@link FromApiJsonDeserializer} for
+ * {@link GuarantorCommand}'s.
+ */
+@Component
+public final class AccrualAccountingDataValidator {
+
+ private final FromJsonHelper fromApiJsonHelper;
+
+ @Autowired
+ public AccrualAccountingDataValidator(final FromJsonHelper fromApiJsonfromApiJsonHelper) {
+ this.fromApiJsonHelper = fromApiJsonfromApiJsonHelper;
+ }
+
+ public void validateLoanPeriodicAccrualData(final String json) {
+ if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+ final Type typeOfMap = new TypeToken