From 41bed0aaceef8c0f3f591ec323a392bc52491c04 Mon Sep 17 00:00:00 2001 From: Madhukar Date: Thu, 17 Jul 2014 11:48:58 +0530 Subject: [PATCH] MIFOSX-1408 Inactivate savings charges --- api-docs/apiLive.htm | 55 +++++++++++++- ...d_savingscharge_inactivate_permissions.sql | 5 ++ .../ClientSavingsIntegrationTest.java | 32 ++++++++- .../common/savings/SavingsAccountHelper.java | 16 ++++- .../commands/domain/CommandWrapper.java | 4 ++ .../service/CommandWrapperBuilder.java | 10 +++ .../SynchronousCommandProcessingService.java | 2 + .../portfolio/charge/data/ChargeData.java | 5 +- .../savings/SavingsApiConstants.java | 1 + .../api/FixedDepositAccountsApiResource.java | 11 +-- .../RecurringDepositAccountsApiResource.java | 13 ++-- .../api/SavingsAccountChargesApiResource.java | 19 ++++- .../api/SavingsAccountsApiResource.java | 12 ++-- .../data/SavingsAccountChargeData.java | 37 ++++------ .../SavingsAccountChargeDataValidator.java | 2 +- .../savings/domain/SavingsAccount.java | 39 +++++++++- .../savings/domain/SavingsAccountCharge.java | 47 +++++++++++- .../domain/SavingsAccountTransaction.java | 18 +++++ ...ateSavingsAccountChargeCommandHandler.java | 27 +++++++ ...vingsAccountChargeReadPlatformService.java | 2 +- ...sAccountChargeReadPlatformServiceImpl.java | 20 ++++-- .../SavingsAccountWritePlatformService.java | 2 + ...WritePlatformServiceJpaRepositoryImpl.java | 71 +++++++++++++++++++ 23 files changed, 393 insertions(+), 57 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V188__add_savingscharge_inactivate_permissions.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/InactivateSavingsAccountChargeCommandHandler.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index aac4d98c8..4581fe480 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -525,7 +525,7 @@ Savings Account Charges savingsaccounts/{accountId}/charges Add a Savings Account Charge - + List Savings Account Charges @@ -553,6 +553,14 @@ + + + savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate + Inactivate a Savings Account Charge + + + + Fixed Deposit Accounts fixeddepositaccounts @@ -18833,6 +18841,11 @@ No Request Body:

Example Requests:

savingsaccounts/1/charges

+
savingsaccounts/1/charges?chargeStatus=all
+
+
savingsaccounts/1/charges?chargeStatus=inactive
+
+
savingsaccounts/1/charges?chargeStatus=active

savingsaccounts/1/charges?fields=name,amountOrPercentage
@@ -19318,7 +19331,7 @@ No Request Body:

Pay a Savings account Charge

- A charge will be paid when savings account is active and having sufficient balance. + An active charge will be paid when savings account is active and having sufficient balance.

@@ -19375,6 +19388,34 @@ No Request Body:
+   +
+
+

Inactivate a Savings account Charge

+

+ A charge will be allowed to inactivate when savings account is active and not having any dues as of today. If charge is overpaid, corresponding charge payment transactions will be reversed. +

+
+
+ +POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate + + +POST savingsaccounts/1/charges/2?command=inactivate +Content-Type: application/json +No Request Body: + + +{ + "officeId": 1, + "clientId": 1, + "savingsId": 1, + "resourceId": 2 +} + +
+
+ @@ -32518,7 +32559,7 @@ No Request Body: Savings Account Charges savingsaccounts/{accountId}/charges Add a Savings Account Charge - + List Savings Account Charges @@ -32545,6 +32586,14 @@ No Request Body: + + + + savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate + Inactivate a Savings Account Charge + + + Fixed Deposit Product diff --git a/mifosng-db/migrations/core_db/V188__add_savingscharge_inactivate_permissions.sql b/mifosng-db/migrations/core_db/V188__add_savingscharge_inactivate_permissions.sql new file mode 100644 index 000000000..0800f0f6d --- /dev/null +++ b/mifosng-db/migrations/core_db/V188__add_savingscharge_inactivate_permissions.sql @@ -0,0 +1,5 @@ +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'INACTIVATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'INACTIVATE', 0); +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'INACTIVATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'INACTIVATE_CHECKER', 0); + +ALTER TABLE `m_savings_account_charge` + ADD COLUMN `inactivated_on_date` DATE NULL DEFAULT NULL AFTER `is_active`; \ No newline at end of file diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientSavingsIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientSavingsIntegrationTest.java index cecc99469..ea8d8c156 100755 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientSavingsIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientSavingsIntegrationTest.java @@ -404,7 +404,10 @@ public class ClientSavingsIntegrationTest { @SuppressWarnings("unchecked") @Test public void testSavingsAccountCharges() { + + final ResponseSpecification erroResponseSpec = new ResponseSpecBuilder().build(); this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, erroResponseSpec); final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); Assert.assertNotNull(clientID); @@ -416,8 +419,8 @@ public class ClientSavingsIntegrationTest { minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); Assert.assertNotNull(savingsProductID); - final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); Assert.assertNotNull(savingsProductID); + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); @@ -457,6 +460,13 @@ public class ClientSavingsIntegrationTest { Assert.assertEquals(1, charges.size()); HashMap savingsChargeForPay = charges.get(0); + Integer annualSavingsChargeId = (Integer) savingsChargeForPay.get("id"); + + ArrayList savingsAccountErrorData = (ArrayList) validationErrorHelper.inactivateCharge(annualSavingsChargeId, + savingsId, CommonConstants.RESPONSE_ERROR); + assertEquals("validation.msg.savingsaccountcharge.inactivation.of.charge.not.allowed.when.charge.is.due", savingsAccountErrorData + .get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + SimpleDateFormat sdf = new SimpleDateFormat(CommonConstants.dateFormat); Calendar cal = Calendar.getInstance(); List dates = (List) savingsChargeForPay.get("dueDate"); @@ -469,6 +479,10 @@ public class ClientSavingsIntegrationTest { HashMap paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForPay.get("id")); assertEquals(savingsChargeForPay.get("amount"), paidCharge.get("amountPaid")); + Integer inactivatedChargeId = (Integer) this.savingsAccountHelper.inactivateCharge(annualSavingsChargeId, savingsId, + CommonConstants.RESPONSE_RESOURCE_ID); + assertEquals("Inactivated Savings Charges Id", annualSavingsChargeId, inactivatedChargeId); + final Integer monthlyFeechargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec, ChargesHelper.getSavingsMonthlyFeeJSON()); Assert.assertNotNull(monthlyFeechargeId); @@ -478,6 +492,13 @@ public class ClientSavingsIntegrationTest { Assert.assertEquals(2, charges.size()); HashMap savingsChargeForWaive = charges.get(1); + final Integer monthlySavingsCharge = (Integer) savingsChargeForWaive.get("id"); + + savingsAccountErrorData = (ArrayList) validationErrorHelper.inactivateCharge(monthlySavingsCharge, savingsId, + CommonConstants.RESPONSE_ERROR); + assertEquals("validation.msg.savingsaccountcharge.inactivation.of.charge.not.allowed.when.charge.is.due", savingsAccountErrorData + .get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + this.savingsAccountHelper.waiveCharge((Integer) savingsChargeForWaive.get("id"), savingsId); HashMap waiveCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForWaive.get("id")); assertEquals(savingsChargeForWaive.get("amount"), waiveCharge.get("amountWaived")); @@ -495,8 +516,15 @@ public class ClientSavingsIntegrationTest { this.savingsAccountHelper.addChargesForSavings(savingsId, weeklyFeeId); charges = this.savingsAccountHelper.getSavingsCharges(savingsId); Assert.assertEquals(3, charges.size()); - + savingsChargeForPay = charges.get(2); + final Integer weeklySavingsFeeId = (Integer) savingsChargeForPay.get("id"); + + savingsAccountErrorData = (ArrayList) validationErrorHelper.inactivateCharge(weeklySavingsFeeId, savingsId, + CommonConstants.RESPONSE_ERROR); + assertEquals("validation.msg.savingsaccountcharge.inactivation.of.charge.not.allowed.when.charge.is.due", savingsAccountErrorData + .get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + cal = Calendar.getInstance(); dates = (List) savingsChargeForPay.get("dueDate"); cal.set(Calendar.YEAR, (Integer) dates.get(0)); diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsAccountHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsAccountHelper.java index 53be5ebac..fdbf36a60 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsAccountHelper.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsAccountHelper.java @@ -383,6 +383,20 @@ public class SavingsAccountHelper { } public HashMap activateSavingsAccount(Integer savingsID, String activationDate) { - return performSavingApplicationActions(createSavingsOperationURL(ACTIVATE_SAVINGS_COMMAND, savingsID), getAccountActivationJSON(activationDate)); + return performSavingApplicationActions(createSavingsOperationURL(ACTIVATE_SAVINGS_COMMAND, savingsID), + getAccountActivationJSON(activationDate)); + } + + public Object inactivateCharge(final Integer chargeId, final Integer savingsId, final String jsonAttributeToGetBack) { + return performSavingActions(createChargesURL("inactivate", savingsId, chargeId), getSavingsInactivateChargeJSON(), + jsonAttributeToGetBack); + } + + private String getSavingsInactivateChargeJSON() { + final HashMap map = new HashMap<>(); + map.put("locale", CommonConstants.locale); + map.put("dateFormat", CommonConstants.dateFormat); + String josn = new Gson().toJson(map); + return josn; } } \ No newline at end of file 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 8e11783f3..45e483fa1 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 @@ -872,4 +872,8 @@ public class CommandWrapper { return this.actionName.equalsIgnoreCase("CREATE"); } + public boolean isInactivateSavingsAccountCharge() { + return this.actionName.equalsIgnoreCase("INACTIVATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE"); + } + } \ 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 d0093fb38..5b1e533aa 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 @@ -1104,6 +1104,16 @@ public class CommandWrapperBuilder { } + public CommandWrapperBuilder inactivateSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { + this.actionName = "INACTIVATE"; + this.entityName = "SAVINGSACCOUNTCHARGE"; + this.entityId = savingsAccountChargeId; + this.savingsId = savingsAccountId; + this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId; + return this; + + } + public CommandWrapperBuilder deleteSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { this.actionName = "DELETE"; this.entityName = "SAVINGSACCOUNTCHARGE"; 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 f6f3885a4..8710e21b9 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 @@ -506,6 +506,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer handler = this.applicationContext.getBean("waiveSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isPaySavingsAccountCharge()) { handler = this.applicationContext.getBean("paySavingsAccountChargeCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isInactivateSavingsAccountCharge()) { + handler = this.applicationContext.getBean("inactivateSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class); } else { throw new UnsupportedCommandException(wrapper.commandName()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java index cf66548c3..fb9ff8d27 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java @@ -206,14 +206,15 @@ public class ChargeData implements Comparable, Serializable { final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO; final Collection chargeOptions = null; final LocalDate dueAsOfDate = null; + final Boolean isActive = null; + final LocalDate inactivationDate = null; return SavingsAccountChargeData.instance(savingsChargeId, this.id, savingsAccountId, this.name, this.currency, this.amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, percentage, amountPercentageAppliedTo, chargeOptions, this.penalty, this.feeOnMonthDay, - this.feeInterval); + this.feeInterval, isActive, inactivationDate); } - public boolean isPenalty() { return this.penalty; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index 5d77d2bc1..eafa8839c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -42,6 +42,7 @@ public class SavingsApiConstants { public static String COMMAND_ADJUST_TRANSACTION = "modify"; public static String COMMAND_WAIVE_CHARGE = "waive"; public static String COMMAND_PAY_CHARGE = "paycharge"; + public static String COMMAND_INACTIVATE_CHARGE = "inactivate"; // general public static final String localeParamName = "locale"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/FixedDepositAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/FixedDepositAccountsApiResource.java index e1fc2e887..c4495d946 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/FixedDepositAccountsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/FixedDepositAccountsApiResource.java @@ -153,16 +153,19 @@ public class FixedDepositAccountsApiResource { @Produces({ MediaType.APPLICATION_JSON }) public String retrieveOne(@PathParam("accountId") final Long accountId, @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @Context final UriInfo uriInfo) { + @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); + if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException( + "status", chargeStatus, new Object[] { "all", "active", "inactive" }); } + final FixedDepositAccountData account = (FixedDepositAccountData) this.depositAccountReadPlatformService.retrieveOneWithChartSlabs( DepositAccountType.FIXED_DEPOSIT, accountId); final Set mandatoryResponseParameters = new HashSet<>(); final FixedDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly, - uriInfo, mandatoryResponseParameters); + chargeStatus, uriInfo, mandatoryResponseParameters); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), mandatoryResponseParameters); @@ -171,7 +174,7 @@ public class FixedDepositAccountsApiResource { } private FixedDepositAccountData populateTemplateAndAssociations(final Long accountId, final FixedDepositAccountData savingsAccount, - final boolean staffInSelectedOfficeOnly, final UriInfo uriInfo, final Set mandatoryResponseParameters) { + final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo, final Set mandatoryResponseParameters) { Collection transactions = null; Collection charges = null; @@ -197,7 +200,7 @@ public class FixedDepositAccountsApiResource { if (associationParameters.contains(SavingsApiConstants.charges)) { mandatoryResponseParameters.add(SavingsApiConstants.charges); final Collection currentCharges = this.savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(accountId); + .retrieveSavingsAccountCharges(accountId, chargeStatus); if (!CollectionUtils.isEmpty(currentCharges)) { charges = currentCharges; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/RecurringDepositAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/RecurringDepositAccountsApiResource.java index 23b792c5f..165701cda 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/RecurringDepositAccountsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/RecurringDepositAccountsApiResource.java @@ -150,16 +150,19 @@ public class RecurringDepositAccountsApiResource { @Produces({ MediaType.APPLICATION_JSON }) public String retrieveOne(@PathParam("accountId") final Long accountId, @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @Context final UriInfo uriInfo) { + @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException( + "status", chargeStatus, new Object[] { "all", "active", "inactive" }); } + final RecurringDepositAccountData account = (RecurringDepositAccountData) this.depositAccountReadPlatformService .retrieveOneWithChartSlabs(DepositAccountType.RECURRING_DEPOSIT, accountId); final Set mandatoryResponseParameters = new HashSet<>(); final RecurringDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly, - uriInfo, mandatoryResponseParameters); + chargeStatus, uriInfo, mandatoryResponseParameters); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), mandatoryResponseParameters); @@ -168,8 +171,8 @@ public class RecurringDepositAccountsApiResource { } private RecurringDepositAccountData populateTemplateAndAssociations(final Long accountId, - final RecurringDepositAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final UriInfo uriInfo, - final Set mandatoryResponseParameters) { + final RecurringDepositAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final String chargeStatus, + final UriInfo uriInfo, final Set mandatoryResponseParameters) { Collection transactions = null; Collection charges = null; @@ -193,7 +196,7 @@ public class RecurringDepositAccountsApiResource { if (associationParameters.contains(SavingsApiConstants.charges)) { mandatoryResponseParameters.add(SavingsApiConstants.charges); final Collection currentCharges = this.savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(accountId); + .retrieveSavingsAccountCharges(accountId, chargeStatus); if (!CollectionUtils.isEmpty(currentCharges)) { charges = currentCharges; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java index 458cb4ed1..2d42b365d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java @@ -9,11 +9,13 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.COMMAND_PA import static org.mifosplatform.portfolio.savings.SavingsApiConstants.COMMAND_WAIVE_CHARGE; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.COMMAND_INACTIVATE_CHARGE; import java.util.Collection; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -79,12 +81,15 @@ public class SavingsAccountChargesApiResource { @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public String retrieveAllSavingsAccountCharges(@PathParam("savingsAccountId") final Long savingsAccountId, - @Context final UriInfo uriInfo) { + @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME); + if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException( + "status", chargeStatus, new Object[] { "all", "active", "inactive" }); } + final Collection savingsAccountCharges = this.savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(savingsAccountId); + .retrieveSavingsAccountCharges(savingsAccountId, chargeStatus); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, savingsAccountCharges, @@ -173,9 +178,17 @@ public class SavingsAccountChargesApiResource { final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + json = this.toApiJsonSerializer.serialize(result); + } else if (is(commandParam, COMMAND_INACTIVATE_CHARGE)) { + final CommandWrapper commandRequest = new CommandWrapperBuilder() + .inactivateSavingsAccountCharge(savingsAccountId, savingsAccountChargeId).withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + json = this.toApiJsonSerializer.serialize(result); } else { - throw new UnrecognizedQueryParamException("command", commandParam, COMMAND_PAY_CHARGE, COMMAND_WAIVE_CHARGE); + throw new UnrecognizedQueryParamException("command", commandParam, COMMAND_PAY_CHARGE, COMMAND_WAIVE_CHARGE, + COMMAND_INACTIVATE_CHARGE); } return json; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java index bfd81382e..b9c1621e4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java @@ -130,15 +130,18 @@ public class SavingsAccountsApiResource { @Produces({ MediaType.APPLICATION_JSON }) public String retrieveOne(@PathParam("accountId") final Long accountId, @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @Context final UriInfo uriInfo) { + @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); + if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException( + "status", chargeStatus, new Object[] { "all", "active", "inactive" }); } + final SavingsAccountData savingsAccount = this.savingsAccountReadPlatformService.retrieveOne(accountId); final Set mandatoryResponseParameters = new HashSet<>(); final SavingsAccountData savingsAccountTemplate = populateTemplateAndAssociations(accountId, savingsAccount, - staffInSelectedOfficeOnly, uriInfo, mandatoryResponseParameters); + staffInSelectedOfficeOnly, chargeStatus, uriInfo, mandatoryResponseParameters); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), mandatoryResponseParameters); @@ -147,7 +150,8 @@ public class SavingsAccountsApiResource { } private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount, - final boolean staffInSelectedOfficeOnly, final UriInfo uriInfo, final Set mandatoryResponseParameters) { + final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo, + final Set mandatoryResponseParameters) { Collection transactions = null; Collection charges = null; @@ -171,7 +175,7 @@ public class SavingsAccountsApiResource { if (associationParameters.contains(SavingsApiConstants.charges)) { mandatoryResponseParameters.add(SavingsApiConstants.charges); final Collection currentCharges = this.savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(accountId); + .retrieveSavingsAccountCharges(accountId, chargeStatus); if (!CollectionUtils.isEmpty(currentCharges)) { charges = currentCharges; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java index 8f0ff9b69..3ececf6fa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java @@ -18,64 +18,53 @@ import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; /** * Immutable data object for Savings Account charge data. */ +@SuppressWarnings("unused") public class SavingsAccountChargeData { - @SuppressWarnings("unused") private final Long id; - @SuppressWarnings("unused") private final Long chargeId; - @SuppressWarnings("unused") private final Long accountId; - @SuppressWarnings("unused") private final String name; private final EnumOptionData chargeTimeType; - @SuppressWarnings("unused") private final LocalDate dueDate; - @SuppressWarnings("unused") private final MonthDay feeOnMonthDay; - @SuppressWarnings("unused") private final Integer feeInterval; private final EnumOptionData chargeCalculationType; private final BigDecimal percentage; - @SuppressWarnings("unused") private final BigDecimal amountPercentageAppliedTo; - @SuppressWarnings("unused") private final CurrencyData currency; private final BigDecimal amount; - @SuppressWarnings("unused") private final BigDecimal amountPaid; - @SuppressWarnings("unused") private final BigDecimal amountWaived; - @SuppressWarnings("unused") private final BigDecimal amountWrittenOff; - @SuppressWarnings("unused") private final BigDecimal amountOutstanding; - @SuppressWarnings("unused") private final BigDecimal amountOrPercentage; - @SuppressWarnings("unused") - private final Collection chargeOptions; - - @SuppressWarnings("unused") private final boolean penalty; + private final Boolean isActive; + + private final LocalDate inactivationDate; + + private final Collection chargeOptions; + public static SavingsAccountChargeData template(final Collection chargeOptions) { final Long id = null; final Long chargeId = null; @@ -95,10 +84,12 @@ public class SavingsAccountChargeData { final LocalDate dueAsOfDate = null; final MonthDay feeOnMonthDay = null; final Integer feeInterval = null; + final Boolean isActive = null; + final LocalDate inactivationDate = null; return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, - penalty, feeOnMonthDay, feeInterval); + penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate); } public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final Long accountId, final String name, @@ -106,11 +97,11 @@ public class SavingsAccountChargeData { final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, final Collection chargeOptions, final boolean penalty, - final MonthDay feeOnMonthDay, final Integer feeInterval) { + final MonthDay feeOnMonthDay, final Integer feeInterval, final Boolean isActive, final LocalDate inactivationDate) { return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, - penalty, feeOnMonthDay, feeInterval); + penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate); } private SavingsAccountChargeData(final Long id, final Long chargeId, final Long accountId, final String name, @@ -118,7 +109,7 @@ public class SavingsAccountChargeData { final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding, final Collection chargeOptions, final boolean penalty, - final MonthDay feeOnMonthDay, final Integer feeInterval) { + final MonthDay feeOnMonthDay, final Integer feeInterval, final Boolean isActive, final LocalDate inactivationDate) { this.id = id; this.chargeId = chargeId; this.accountId = accountId; @@ -139,6 +130,8 @@ public class SavingsAccountChargeData { this.penalty = penalty; this.feeOnMonthDay = feeOnMonthDay; this.feeInterval = feeInterval; + this.isActive = isActive; + this.inactivationDate = inactivationDate; } private BigDecimal getAmountOrPercentage() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java index e411552e5..b9695ee44 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java @@ -11,8 +11,8 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_AC import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeIntervalParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import java.lang.reflect.Type; import java.math.BigDecimal; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 454beb82c..40541955a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -769,7 +769,7 @@ public class SavingsAccount extends AbstractPersistable { private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate) { for (SavingsAccountCharge charge : this.charges()) { - if (charge.isWithdrawalFee()) { + if (charge.isWithdrawalFee() && charge.isActive()) { charge.updateWithdralFeeAmount(transactionAmoount); this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate); } @@ -1854,6 +1854,11 @@ public class SavingsAccount extends AbstractPersistable { final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId); + if (savingsAccountCharge.isNotActive()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.not.active"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + if (savingsAccountCharge.isWithdrawalFee()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.waiver.of.withdrawal.fee.not.supported"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -1977,6 +1982,11 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + if (savingsAccountCharge.isNotActive()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.not.active"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + if (getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); @@ -2182,4 +2192,31 @@ public class SavingsAccount extends AbstractPersistable { public BigDecimal minBalanceForInterestCalculation() { return this.minBalanceForInterestCalculation; } + + public void inactivateCharge(SavingsAccountCharge savingsAccountCharge, LocalDate inactivationOnDate) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + if (isClosed()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (isNotActive()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + savingsAccountCharge.inactiavateCharge(inactivationOnDate); + } + + public SavingsAccountCharge getUpdatedChargeDetails(SavingsAccountCharge savingsAccountCharge) { + for (final SavingsAccountCharge charge : this.charges) { + if (charge.equals(savingsAccountCharge)) { + savingsAccountCharge = charge; + break; + } + } + return savingsAccountCharge; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java index 1a898e6ed..67c6d1a56 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java @@ -104,6 +104,10 @@ public class SavingsAccountCharge extends AbstractPersistable { @Column(name = "is_active", nullable = false) private boolean status = true; + + @Temporal(TemporalType.DATE) + @Column(name = "inactivated_on_date") + private Date inactivationDate; public static SavingsAccountCharge createNewFromJson(final SavingsAccount savingsAccount, final Charge chargeDefinition, final JsonCommand command) { @@ -302,6 +306,7 @@ public class SavingsAccountCharge extends AbstractPersistable { resetPropertiesForRecurringFees(); updateToPreviousDueDate();// reset annual and monthly due date. this.paid = false; + this.status = true; } public Money waive(final MonetaryCurrency currency) { @@ -323,6 +328,7 @@ public class SavingsAccountCharge extends AbstractPersistable { this.amountWaived = amountWaived.getAmount(); this.amountOutstanding = calculateAmountOutstanding(currency); this.waived = false; + this.status = true; resetPropertiesForRecurringFees(); updateToPreviousDueDate(); @@ -761,14 +767,15 @@ public class SavingsAccountCharge extends AbstractPersistable { LocalDate nextDueLocalDate = new LocalDate(dueDate); if (isAnnualFee()) { nextDueLocalDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).minusYears(1); + nextDueLocalDate = setDayOfMonth(nextDueLocalDate); } else if (isMonthlyFee()) { nextDueLocalDate = nextDueLocalDate.minusMonths(this.feeInterval); + nextDueLocalDate = setDayOfMonth(nextDueLocalDate); } else if (isWeeklyFee()) { nextDueLocalDate = nextDueLocalDate.minusDays(7 * this.feeInterval); + nextDueLocalDate = setDayOfWeek(nextDueLocalDate); } - nextDueLocalDate = setDayOfMonth(nextDueLocalDate); - this.dueDate = nextDueLocalDate.toDate(); } } @@ -780,4 +787,40 @@ public class SavingsAccountCharge extends AbstractPersistable { public boolean feeSettingsSet() { return this.feeOnDay != null && this.feeOnMonth != null; } + + public boolean isRecurringFee() { + return isWeeklyFee() || isMonthlyFee() || isAnnualFee(); + } + + public boolean isChargeIsDue(final LocalDate nextDueDate) { + return this.getDueLocalDate().isBefore(nextDueDate); + } + + public boolean isChargeIsOverPaid(final LocalDate nextDueDate) { + final BigDecimal amountPaid = this.amountPaid == null ? BigDecimal.ZERO : amountPaid(); + return this.getDueLocalDate().isAfter(nextDueDate) && amountPaid.compareTo(BigDecimal.ZERO) == 1; + } + + private BigDecimal amountPaid() { + return this.amountPaid; + } + + public LocalDate nextDuDate(final LocalDate date) { + return calculateNextDueDate(date); + } + + public void inactiavateCharge(final LocalDate inactivationOnDate) { + this.inactivationDate = inactivationOnDate.toDate(); + this.status = false; + this.amountOutstanding = BigDecimal.ZERO; + this.paid = true; + } + + public boolean isActive() { + return this.status; + } + + public boolean isNotActive() { + return !isActive(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index f03f326c2..3ad6a8239 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -567,4 +567,22 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return this.createdDate; } + public boolean isPaymentForCurrentCharge(final SavingsAccountCharge savingsAccountCharge) { + + final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); + final boolean isChargePaidForCurrentCharge; + if (chargePaidBy == null) { + isChargePaidForCurrentCharge = false; + } else if (chargePaidBy.getSavingsAccountCharge().equals(savingsAccountCharge)) { + isChargePaidForCurrentCharge = true; + } else { + isChargePaidForCurrentCharge = false; + } + + return isChargePaidForCurrentCharge; + } + + public BigDecimal getAmount() { + return this.amount; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/InactivateSavingsAccountChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/InactivateSavingsAccountChargeCommandHandler.java new file mode 100644 index 000000000..de0f6f2b1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/InactivateSavingsAccountChargeCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.savings.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.savings.service.SavingsAccountWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class InactivateSavingsAccountChargeCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public InactivateSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return this.writePlatformService.inactivateCharge(command.getSavingsId(), command.entityId()); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java index 6de5b8bd4..217d28291 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java @@ -15,7 +15,7 @@ public interface SavingsAccountChargeReadPlatformService { ChargeData retrieveSavingsAccountChargeTemplate(); - Collection retrieveSavingsAccountCharges(Long savingsAccountId); + Collection retrieveSavingsAccountCharges(Long savingsAccountId, String status); SavingsAccountChargeData retrieveSavingsAccountChargeDetails(Long savingsAccountChargeId, Long savingsAccountId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java index ec1ffc4f4..0d6d880bc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java @@ -71,6 +71,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou + "sc.fee_on_month as feeOnMonth, " + "sc.fee_on_day as feeOnDay, sc.fee_interval as feeInterval, " + "sc.charge_calculation_enum as chargeCalculation, " + + "sc.is_active as isActive, sc.inactivated_on_date as inactivationDate, " + "c.currency_code as currencyCode, oc.name as currencyName, " + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " + "oc.internationalized_name_code as currencyNameCode from m_charge c " @@ -119,12 +120,14 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou final int chargeCalculation = rs.getInt("chargeCalculation"); final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); final boolean penalty = rs.getBoolean("penalty"); + final Boolean isActive = rs.getBoolean("isActive"); + final LocalDate inactivationDate = JdbcSupport.getLocalDate(rs, "inactivationDate"); final Collection chargeOptions = null; return SavingsAccountChargeData.instance(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, - amountPercentageAppliedTo, chargeOptions, penalty, feeOnMonthDay, feeInterval); + amountPercentageAppliedTo, chargeOptions, penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate); } } @@ -166,15 +169,20 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou } @Override - public Collection retrieveSavingsAccountCharges(final Long loanId) { + public Collection retrieveSavingsAccountCharges(final Long loanId, final String status) { this.context.authenticatedUser(); final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper(); + final StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("select ").append(rm.schema()).append(" where sc.savings_account_id=? "); + if (status.equalsIgnoreCase("active")) { + sqlBuilder.append(" and sc.is_active = 1 "); + } else if (status.equalsIgnoreCase("inactive")) { + sqlBuilder.append(" and sc.is_active = 0 "); + } + sqlBuilder.append(" order by sc.charge_time_enum ASC, sc.charge_due_date ASC, sc.is_penalty ASC"); - final String sql = "select " + rm.schema() + " where sc.savings_account_id=? " - + " order by sc.charge_time_enum ASC, sc.charge_due_date ASC, sc.is_penalty ASC"; - - return this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); + return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { loanId }); } private static final class SavingsAccountChargeDueMapper implements RowMapper { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java index 9276b4a77..683ad3bbf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -55,6 +55,8 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId); CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); + + CommandProcessingResult inactivateCharge(Long savingsAccountId, Long savingsAccountChargeId); void applyChargeDue(final Long savingsAccountChargeId, final Long accountId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 753bcdb1f..f866c5dfa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.savings.service; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; @@ -81,6 +82,7 @@ import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; @Service public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements SavingsAccountWritePlatformService { @@ -938,4 +940,73 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); } + + @Override + public CommandProcessingResult inactivateCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { + + this.context.authenticatedUser(); + + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); + + final SavingsAccount account = savingsAccountCharge.savingsAccount(); + this.savingAccountAssembler.assignSavingAccountHelpers(account); + + final LocalDate inactivationOnDate = DateUtils.getLocalDateOfTenant(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME); + + /*** + * Only recurring fees are allowed to inactivate + */ + if (!savingsAccountCharge.isRecurringFee()) { + baseDataValidator.reset().parameter(null).value(savingsAccountCharge.getId()) + .failWithCodeNoParameterAddedToErrorCode("charge.inactivation.allowed.only.for.recurring.charges"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + + } else { + final LocalDate nextDueDate = savingsAccountCharge.nextDuDate(inactivationOnDate); + + if (savingsAccountCharge.isChargeIsDue(nextDueDate)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("inactivation.of.charge.not.allowed.when.charge.is.due"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } else if (savingsAccountCharge.isChargeIsOverPaid(nextDueDate)) { + + final List chargePayments = new ArrayList<>(); + SavingsAccountCharge updatedCharge = savingsAccountCharge; + do { + chargePayments.clear(); + for (SavingsAccountTransaction transaction : account.getTransactions()) { + if (transaction.isPayCharge() && transaction.isNotReversed() + && transaction.isPaymentForCurrentCharge(savingsAccountCharge)) { + chargePayments.add(transaction); + } + } + /*** + * Reverse the excess payments of charge transactions + */ + SavingsAccountTransaction lastChargePayment = getLastChargePayment(chargePayments); + this.undoTransaction(savingsAccountCharge.savingsAccount().getId(), lastChargePayment.getId(), false); + updatedCharge = account.getUpdatedChargeDetails(savingsAccountCharge); + } while (updatedCharge.isChargeIsOverPaid(nextDueDate)); + } + account.inactivateCharge(savingsAccountCharge, inactivationOnDate); + } + + return new CommandProcessingResultBuilder() // + .withEntityId(savingsAccountCharge.getId()) // + .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) // + .withClientId(savingsAccountCharge.savingsAccount().clientId()) // + .withGroupId(savingsAccountCharge.savingsAccount().groupId()) // + .withSavingsId(savingsAccountCharge.savingsAccount().getId()) // + .build(); + } + + private SavingsAccountTransaction getLastChargePayment(final List chargePayments) { + if (!CollectionUtils.isEmpty(chargePayments)) { return chargePayments.get(chargePayments.size() - 1); } + return null; + } + } \ No newline at end of file