MIFOSX-1408 Inactivate savings charges

This commit is contained in:
Madhukar
2014-07-19 14:08:05 +05:30
parent d45270c413
commit 41bed0aace
23 changed files with 393 additions and 57 deletions
+52 -3
View File
@@ -525,7 +525,7 @@
<td><a href="#savings_charges">Savings Account Charges</a></td> <td><a href="#savings_charges">Savings Account Charges</a></td>
<td>savingsaccounts/{accountId}/charges</td> <td>savingsaccounts/{accountId}/charges</td>
<td><a href="#savings_charges_create">Add a Savings Account Charge</a></td> <td><a href="#savings_charges_create">Add a Savings Account Charge</a></td>
<td></td> <td><a href="#savings_charges_list">List Savings Account Charges</a></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
@@ -553,6 +553,14 @@
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td></td>
<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate</td>
<td><a href="#savings_charges_inactivate">Inactivate a Savings Account Charge</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr> <tr>
<td><a href="#fdaccounts">Fixed Deposit Accounts</a></td> <td><a href="#fdaccounts">Fixed Deposit Accounts</a></td>
<td>fixeddepositaccounts</td> <td>fixeddepositaccounts</td>
@@ -18833,6 +18841,11 @@ No Request Body:
<p>Example Requests:</p> <p>Example Requests:</p>
<div class=apiClick>savingsaccounts/1/charges</div> <div class=apiClick>savingsaccounts/1/charges</div>
<br> <br>
<div class=apiClick>savingsaccounts/1/charges?chargeStatus=all</div>
<br>
<div class=apiClick>savingsaccounts/1/charges?chargeStatus=inactive</div>
<br>
<div class=apiClick>savingsaccounts/1/charges?chargeStatus=active</div>
<br> <br>
<div class=apiClick>savingsaccounts/1/charges?fields=name,amountOrPercentage</div> <div class=apiClick>savingsaccounts/1/charges?fields=name,amountOrPercentage</div>
</div> </div>
@@ -19318,7 +19331,7 @@ No Request Body:
<div class="method-description"> <div class="method-description">
<h4>Pay a Savings account Charge</h4> <h4>Pay a Savings account Charge</h4>
<p> <p>
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.
</p> </p>
</div> </div>
<div class="method-example"> <div class="method-example">
@@ -19375,6 +19388,34 @@ No Request Body:
</div> </div>
</div> </div>
<a id="savings_charges_inactivate" name="savings_charges_inactivate" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Inactivate a Savings account Charge</h4>
<p>
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.
</p>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate
</code>
<code class="method-request">
POST savingsaccounts/1/charges/2?command=inactivate
Content-Type: application/json
No Request Body:
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 1,
"savingsId": 1,
"resourceId": 2
}
</code>
</div>
</div>
<!-- End of Savings account charges --> <!-- End of Savings account charges -->
<!-- start of fixed deposit accounts api --> <!-- start of fixed deposit accounts api -->
@@ -32518,7 +32559,7 @@ No Request Body:
<td><a href="#savings_charges">Savings Account Charges</a></td> <td><a href="#savings_charges">Savings Account Charges</a></td>
<td>savingsaccounts/{accountId}/charges</td> <td>savingsaccounts/{accountId}/charges</td>
<td><a href="#savings_charges_create">Add a Savings Account Charge</a></td> <td><a href="#savings_charges_create">Add a Savings Account Charge</a></td>
<td></td> <td><a href="#savings_charges_list">List Savings Account Charges</a></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
@@ -32545,6 +32586,14 @@ No Request Body:
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
</tr>
<tr>
<td></td>
<td>savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=inactivate</td>
<td><a href="#savings_charges_inactivate">Inactivate a Savings Account Charge</a></td>
<td></td>
<td></td>
<td></td>
</tr> </tr>
<tr> <tr>
<td><a href="#fdproducts">Fixed Deposit Product</a></td> <td><a href="#fdproducts">Fixed Deposit Product</a></td>
@@ -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`;
@@ -404,7 +404,10 @@ public class ClientSavingsIntegrationTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void testSavingsAccountCharges() { public void testSavingsAccountCharges() {
final ResponseSpecification erroResponseSpec = new ResponseSpecBuilder().build();
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); 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); final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
Assert.assertNotNull(clientID); Assert.assertNotNull(clientID);
@@ -416,8 +419,8 @@ public class ClientSavingsIntegrationTest {
minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
Assert.assertNotNull(savingsProductID); Assert.assertNotNull(savingsProductID);
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
Assert.assertNotNull(savingsProductID); Assert.assertNotNull(savingsProductID);
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
@@ -457,6 +460,13 @@ public class ClientSavingsIntegrationTest {
Assert.assertEquals(1, charges.size()); Assert.assertEquals(1, charges.size());
HashMap savingsChargeForPay = charges.get(0); HashMap savingsChargeForPay = charges.get(0);
Integer annualSavingsChargeId = (Integer) savingsChargeForPay.get("id");
ArrayList<HashMap> savingsAccountErrorData = (ArrayList<HashMap>) 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); SimpleDateFormat sdf = new SimpleDateFormat(CommonConstants.dateFormat);
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
List dates = (List) savingsChargeForPay.get("dueDate"); List dates = (List) savingsChargeForPay.get("dueDate");
@@ -469,6 +479,10 @@ public class ClientSavingsIntegrationTest {
HashMap paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForPay.get("id")); HashMap paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForPay.get("id"));
assertEquals(savingsChargeForPay.get("amount"), paidCharge.get("amountPaid")); 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, final Integer monthlyFeechargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
ChargesHelper.getSavingsMonthlyFeeJSON()); ChargesHelper.getSavingsMonthlyFeeJSON());
Assert.assertNotNull(monthlyFeechargeId); Assert.assertNotNull(monthlyFeechargeId);
@@ -478,6 +492,13 @@ public class ClientSavingsIntegrationTest {
Assert.assertEquals(2, charges.size()); Assert.assertEquals(2, charges.size());
HashMap savingsChargeForWaive = charges.get(1); HashMap savingsChargeForWaive = charges.get(1);
final Integer monthlySavingsCharge = (Integer) savingsChargeForWaive.get("id");
savingsAccountErrorData = (ArrayList<HashMap>) 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); this.savingsAccountHelper.waiveCharge((Integer) savingsChargeForWaive.get("id"), savingsId);
HashMap waiveCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForWaive.get("id")); HashMap waiveCharge = this.savingsAccountHelper.getSavingsCharge(savingsId, (Integer) savingsChargeForWaive.get("id"));
assertEquals(savingsChargeForWaive.get("amount"), waiveCharge.get("amountWaived")); assertEquals(savingsChargeForWaive.get("amount"), waiveCharge.get("amountWaived"));
@@ -495,8 +516,15 @@ public class ClientSavingsIntegrationTest {
this.savingsAccountHelper.addChargesForSavings(savingsId, weeklyFeeId); this.savingsAccountHelper.addChargesForSavings(savingsId, weeklyFeeId);
charges = this.savingsAccountHelper.getSavingsCharges(savingsId); charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
Assert.assertEquals(3, charges.size()); Assert.assertEquals(3, charges.size());
savingsChargeForPay = charges.get(2); savingsChargeForPay = charges.get(2);
final Integer weeklySavingsFeeId = (Integer) savingsChargeForPay.get("id");
savingsAccountErrorData = (ArrayList<HashMap>) 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(); cal = Calendar.getInstance();
dates = (List) savingsChargeForPay.get("dueDate"); dates = (List) savingsChargeForPay.get("dueDate");
cal.set(Calendar.YEAR, (Integer) dates.get(0)); cal.set(Calendar.YEAR, (Integer) dates.get(0));
@@ -383,6 +383,20 @@ public class SavingsAccountHelper {
} }
public HashMap activateSavingsAccount(Integer savingsID, String activationDate) { 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<String, Object> map = new HashMap<>();
map.put("locale", CommonConstants.locale);
map.put("dateFormat", CommonConstants.dateFormat);
String josn = new Gson().toJson(map);
return josn;
} }
} }
@@ -872,4 +872,8 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("CREATE"); return this.actionName.equalsIgnoreCase("CREATE");
} }
public boolean isInactivateSavingsAccountCharge() {
return this.actionName.equalsIgnoreCase("INACTIVATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
}
} }
@@ -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) { public CommandWrapperBuilder deleteSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
this.actionName = "DELETE"; this.actionName = "DELETE";
this.entityName = "SAVINGSACCOUNTCHARGE"; this.entityName = "SAVINGSACCOUNTCHARGE";
@@ -506,6 +506,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("waiveSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("waiveSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isPaySavingsAccountCharge()) { } else if (wrapper.isPaySavingsAccountCharge()) {
handler = this.applicationContext.getBean("paySavingsAccountChargeCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("paySavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isInactivateSavingsAccountCharge()) {
handler = this.applicationContext.getBean("inactivateSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
} else { } else {
throw new UnsupportedCommandException(wrapper.commandName()); throw new UnsupportedCommandException(wrapper.commandName());
} }
@@ -206,14 +206,15 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO; final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
final Collection<ChargeData> chargeOptions = null; final Collection<ChargeData> chargeOptions = null;
final LocalDate dueAsOfDate = 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, return SavingsAccountChargeData.instance(savingsChargeId, this.id, savingsAccountId, this.name, this.currency, this.amount,
amountPaid, amountWaived, amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate,
this.chargeCalculationType, percentage, amountPercentageAppliedTo, chargeOptions, this.penalty, this.feeOnMonthDay, this.chargeCalculationType, percentage, amountPercentageAppliedTo, chargeOptions, this.penalty, this.feeOnMonthDay,
this.feeInterval); this.feeInterval, isActive, inactivationDate);
} }
public boolean isPenalty() { public boolean isPenalty() {
return this.penalty; return this.penalty;
} }
@@ -42,6 +42,7 @@ public class SavingsApiConstants {
public static String COMMAND_ADJUST_TRANSACTION = "modify"; public static String COMMAND_ADJUST_TRANSACTION = "modify";
public static String COMMAND_WAIVE_CHARGE = "waive"; public static String COMMAND_WAIVE_CHARGE = "waive";
public static String COMMAND_PAY_CHARGE = "paycharge"; public static String COMMAND_PAY_CHARGE = "paycharge";
public static String COMMAND_INACTIVATE_CHARGE = "inactivate";
// general // general
public static final String localeParamName = "locale"; public static final String localeParamName = "locale";
@@ -153,16 +153,19 @@ public class FixedDepositAccountsApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@PathParam("accountId") final Long accountId, public String retrieveOne(@PathParam("accountId") final Long accountId,
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, @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); 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( final FixedDepositAccountData account = (FixedDepositAccountData) this.depositAccountReadPlatformService.retrieveOneWithChartSlabs(
DepositAccountType.FIXED_DEPOSIT, accountId); DepositAccountType.FIXED_DEPOSIT, accountId);
final Set<String> mandatoryResponseParameters = new HashSet<>(); final Set<String> mandatoryResponseParameters = new HashSet<>();
final FixedDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly, final FixedDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly,
uriInfo, mandatoryResponseParameters); chargeStatus, uriInfo, mandatoryResponseParameters);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters); mandatoryResponseParameters);
@@ -171,7 +174,7 @@ public class FixedDepositAccountsApiResource {
} }
private FixedDepositAccountData populateTemplateAndAssociations(final Long accountId, final FixedDepositAccountData savingsAccount, private FixedDepositAccountData populateTemplateAndAssociations(final Long accountId, final FixedDepositAccountData savingsAccount,
final boolean staffInSelectedOfficeOnly, final UriInfo uriInfo, final Set<String> mandatoryResponseParameters) { final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo, final Set<String> mandatoryResponseParameters) {
Collection<SavingsAccountTransactionData> transactions = null; Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsAccountChargeData> charges = null; Collection<SavingsAccountChargeData> charges = null;
@@ -197,7 +200,7 @@ public class FixedDepositAccountsApiResource {
if (associationParameters.contains(SavingsApiConstants.charges)) { if (associationParameters.contains(SavingsApiConstants.charges)) {
mandatoryResponseParameters.add(SavingsApiConstants.charges); mandatoryResponseParameters.add(SavingsApiConstants.charges);
final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId); .retrieveSavingsAccountCharges(accountId, chargeStatus);
if (!CollectionUtils.isEmpty(currentCharges)) { if (!CollectionUtils.isEmpty(currentCharges)) {
charges = currentCharges; charges = currentCharges;
} }
@@ -150,16 +150,19 @@ public class RecurringDepositAccountsApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@PathParam("accountId") final Long accountId, public String retrieveOne(@PathParam("accountId") final Long accountId,
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, @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); 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 final RecurringDepositAccountData account = (RecurringDepositAccountData) this.depositAccountReadPlatformService
.retrieveOneWithChartSlabs(DepositAccountType.RECURRING_DEPOSIT, accountId); .retrieveOneWithChartSlabs(DepositAccountType.RECURRING_DEPOSIT, accountId);
final Set<String> mandatoryResponseParameters = new HashSet<>(); final Set<String> mandatoryResponseParameters = new HashSet<>();
final RecurringDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly, final RecurringDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly,
uriInfo, mandatoryResponseParameters); chargeStatus, uriInfo, mandatoryResponseParameters);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters); mandatoryResponseParameters);
@@ -168,8 +171,8 @@ public class RecurringDepositAccountsApiResource {
} }
private RecurringDepositAccountData populateTemplateAndAssociations(final Long accountId, private RecurringDepositAccountData populateTemplateAndAssociations(final Long accountId,
final RecurringDepositAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final UriInfo uriInfo, final RecurringDepositAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final String chargeStatus,
final Set<String> mandatoryResponseParameters) { final UriInfo uriInfo, final Set<String> mandatoryResponseParameters) {
Collection<SavingsAccountTransactionData> transactions = null; Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsAccountChargeData> charges = null; Collection<SavingsAccountChargeData> charges = null;
@@ -193,7 +196,7 @@ public class RecurringDepositAccountsApiResource {
if (associationParameters.contains(SavingsApiConstants.charges)) { if (associationParameters.contains(SavingsApiConstants.charges)) {
mandatoryResponseParameters.add(SavingsApiConstants.charges); mandatoryResponseParameters.add(SavingsApiConstants.charges);
final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId); .retrieveSavingsAccountCharges(accountId, chargeStatus);
if (!CollectionUtils.isEmpty(currentCharges)) { if (!CollectionUtils.isEmpty(currentCharges)) {
charges = currentCharges; charges = currentCharges;
} }
@@ -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.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_CHARGES_RESPONSE_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME; 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 java.util.Collection;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.PUT; import javax.ws.rs.PUT;
@@ -79,12 +81,15 @@ public class SavingsAccountChargesApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retrieveAllSavingsAccountCharges(@PathParam("savingsAccountId") final Long savingsAccountId, 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); 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<SavingsAccountChargeData> savingsAccountCharges = this.savingsAccountChargeReadPlatformService final Collection<SavingsAccountChargeData> savingsAccountCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(savingsAccountId); .retrieveSavingsAccountCharges(savingsAccountId, chargeStatus);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, savingsAccountCharges, return this.toApiJsonSerializer.serialize(settings, savingsAccountCharges,
@@ -173,9 +178,17 @@ public class SavingsAccountChargesApiResource {
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); 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); json = this.toApiJsonSerializer.serialize(result);
} else { } 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; return json;
@@ -130,15 +130,18 @@ public class SavingsAccountsApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@PathParam("accountId") final Long accountId, public String retrieveOne(@PathParam("accountId") final Long accountId,
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, @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); 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 SavingsAccountData savingsAccount = this.savingsAccountReadPlatformService.retrieveOne(accountId);
final Set<String> mandatoryResponseParameters = new HashSet<>(); final Set<String> mandatoryResponseParameters = new HashSet<>();
final SavingsAccountData savingsAccountTemplate = populateTemplateAndAssociations(accountId, savingsAccount, final SavingsAccountData savingsAccountTemplate = populateTemplateAndAssociations(accountId, savingsAccount,
staffInSelectedOfficeOnly, uriInfo, mandatoryResponseParameters); staffInSelectedOfficeOnly, chargeStatus, uriInfo, mandatoryResponseParameters);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters); mandatoryResponseParameters);
@@ -147,7 +150,8 @@ public class SavingsAccountsApiResource {
} }
private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount, private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount,
final boolean staffInSelectedOfficeOnly, final UriInfo uriInfo, final Set<String> mandatoryResponseParameters) { final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo,
final Set<String> mandatoryResponseParameters) {
Collection<SavingsAccountTransactionData> transactions = null; Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsAccountChargeData> charges = null; Collection<SavingsAccountChargeData> charges = null;
@@ -171,7 +175,7 @@ public class SavingsAccountsApiResource {
if (associationParameters.contains(SavingsApiConstants.charges)) { if (associationParameters.contains(SavingsApiConstants.charges)) {
mandatoryResponseParameters.add(SavingsApiConstants.charges); mandatoryResponseParameters.add(SavingsApiConstants.charges);
final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId); .retrieveSavingsAccountCharges(accountId, chargeStatus);
if (!CollectionUtils.isEmpty(currentCharges)) { if (!CollectionUtils.isEmpty(currentCharges)) {
charges = currentCharges; charges = currentCharges;
} }
@@ -18,64 +18,53 @@ import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
/** /**
* Immutable data object for Savings Account charge data. * Immutable data object for Savings Account charge data.
*/ */
@SuppressWarnings("unused")
public class SavingsAccountChargeData { public class SavingsAccountChargeData {
@SuppressWarnings("unused")
private final Long id; private final Long id;
@SuppressWarnings("unused")
private final Long chargeId; private final Long chargeId;
@SuppressWarnings("unused")
private final Long accountId; private final Long accountId;
@SuppressWarnings("unused")
private final String name; private final String name;
private final EnumOptionData chargeTimeType; private final EnumOptionData chargeTimeType;
@SuppressWarnings("unused")
private final LocalDate dueDate; private final LocalDate dueDate;
@SuppressWarnings("unused")
private final MonthDay feeOnMonthDay; private final MonthDay feeOnMonthDay;
@SuppressWarnings("unused")
private final Integer feeInterval; private final Integer feeInterval;
private final EnumOptionData chargeCalculationType; private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage; private final BigDecimal percentage;
@SuppressWarnings("unused")
private final BigDecimal amountPercentageAppliedTo; private final BigDecimal amountPercentageAppliedTo;
@SuppressWarnings("unused")
private final CurrencyData currency; private final CurrencyData currency;
private final BigDecimal amount; private final BigDecimal amount;
@SuppressWarnings("unused")
private final BigDecimal amountPaid; private final BigDecimal amountPaid;
@SuppressWarnings("unused")
private final BigDecimal amountWaived; private final BigDecimal amountWaived;
@SuppressWarnings("unused")
private final BigDecimal amountWrittenOff; private final BigDecimal amountWrittenOff;
@SuppressWarnings("unused")
private final BigDecimal amountOutstanding; private final BigDecimal amountOutstanding;
@SuppressWarnings("unused")
private final BigDecimal amountOrPercentage; private final BigDecimal amountOrPercentage;
@SuppressWarnings("unused")
private final Collection<ChargeData> chargeOptions;
@SuppressWarnings("unused")
private final boolean penalty; private final boolean penalty;
private final Boolean isActive;
private final LocalDate inactivationDate;
private final Collection<ChargeData> chargeOptions;
public static SavingsAccountChargeData template(final Collection<ChargeData> chargeOptions) { public static SavingsAccountChargeData template(final Collection<ChargeData> chargeOptions) {
final Long id = null; final Long id = null;
final Long chargeId = null; final Long chargeId = null;
@@ -95,10 +84,12 @@ public class SavingsAccountChargeData {
final LocalDate dueAsOfDate = null; final LocalDate dueAsOfDate = null;
final MonthDay feeOnMonthDay = null; final MonthDay feeOnMonthDay = null;
final Integer feeInterval = null; final Integer feeInterval = null;
final Boolean isActive = null;
final LocalDate inactivationDate = null;
return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, 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, 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 BigDecimal amountWrittenOff, final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType,
final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage, final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo, final Collection<ChargeData> chargeOptions, final boolean penalty, final BigDecimal amountPercentageAppliedTo, final Collection<ChargeData> 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, return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, 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, 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 percentage, final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount,
final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
final BigDecimal amountOutstanding, final Collection<ChargeData> chargeOptions, final boolean penalty, final BigDecimal amountOutstanding, final Collection<ChargeData> 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.id = id;
this.chargeId = chargeId; this.chargeId = chargeId;
this.accountId = accountId; this.accountId = accountId;
@@ -139,6 +130,8 @@ public class SavingsAccountChargeData {
this.penalty = penalty; this.penalty = penalty;
this.feeOnMonthDay = feeOnMonthDay; this.feeOnMonthDay = feeOnMonthDay;
this.feeInterval = feeInterval; this.feeInterval = feeInterval;
this.isActive = isActive;
this.inactivationDate = inactivationDate;
} }
private BigDecimal getAmountOrPercentage() { private BigDecimal getAmountOrPercentage() {
@@ -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.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; 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.feeIntervalParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -769,7 +769,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate) { private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate) {
for (SavingsAccountCharge charge : this.charges()) { for (SavingsAccountCharge charge : this.charges()) {
if (charge.isWithdrawalFee()) { if (charge.isWithdrawalFee() && charge.isActive()) {
charge.updateWithdralFeeAmount(transactionAmoount); charge.updateWithdralFeeAmount(transactionAmoount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate); this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate);
} }
@@ -1854,6 +1854,11 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId); 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()) { if (savingsAccountCharge.isWithdrawalFee()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.waiver.of.withdrawal.fee.not.supported"); baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.waiver.of.withdrawal.fee.not.supported");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
@@ -1977,6 +1982,11 @@ public class SavingsAccount extends AbstractPersistable<Long> {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } 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())) { if (getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate");
@@ -2182,4 +2192,31 @@ public class SavingsAccount extends AbstractPersistable<Long> {
public BigDecimal minBalanceForInterestCalculation() { public BigDecimal minBalanceForInterestCalculation() {
return this.minBalanceForInterestCalculation; return this.minBalanceForInterestCalculation;
} }
public void inactivateCharge(SavingsAccountCharge savingsAccountCharge, LocalDate inactivationOnDate) {
final List<ApiParameterError> 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;
}
} }
@@ -104,6 +104,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
@Column(name = "is_active", nullable = false) @Column(name = "is_active", nullable = false)
private boolean status = true; 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, public static SavingsAccountCharge createNewFromJson(final SavingsAccount savingsAccount, final Charge chargeDefinition,
final JsonCommand command) { final JsonCommand command) {
@@ -302,6 +306,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
resetPropertiesForRecurringFees(); resetPropertiesForRecurringFees();
updateToPreviousDueDate();// reset annual and monthly due date. updateToPreviousDueDate();// reset annual and monthly due date.
this.paid = false; this.paid = false;
this.status = true;
} }
public Money waive(final MonetaryCurrency currency) { public Money waive(final MonetaryCurrency currency) {
@@ -323,6 +328,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.amountWaived = amountWaived.getAmount(); this.amountWaived = amountWaived.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency); this.amountOutstanding = calculateAmountOutstanding(currency);
this.waived = false; this.waived = false;
this.status = true;
resetPropertiesForRecurringFees(); resetPropertiesForRecurringFees();
updateToPreviousDueDate(); updateToPreviousDueDate();
@@ -761,14 +767,15 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
LocalDate nextDueLocalDate = new LocalDate(dueDate); LocalDate nextDueLocalDate = new LocalDate(dueDate);
if (isAnnualFee()) { if (isAnnualFee()) {
nextDueLocalDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).minusYears(1); nextDueLocalDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).minusYears(1);
nextDueLocalDate = setDayOfMonth(nextDueLocalDate);
} else if (isMonthlyFee()) { } else if (isMonthlyFee()) {
nextDueLocalDate = nextDueLocalDate.minusMonths(this.feeInterval); nextDueLocalDate = nextDueLocalDate.minusMonths(this.feeInterval);
nextDueLocalDate = setDayOfMonth(nextDueLocalDate);
} else if (isWeeklyFee()) { } else if (isWeeklyFee()) {
nextDueLocalDate = nextDueLocalDate.minusDays(7 * this.feeInterval); nextDueLocalDate = nextDueLocalDate.minusDays(7 * this.feeInterval);
nextDueLocalDate = setDayOfWeek(nextDueLocalDate);
} }
nextDueLocalDate = setDayOfMonth(nextDueLocalDate);
this.dueDate = nextDueLocalDate.toDate(); this.dueDate = nextDueLocalDate.toDate();
} }
} }
@@ -780,4 +787,40 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
public boolean feeSettingsSet() { public boolean feeSettingsSet() {
return this.feeOnDay != null && this.feeOnMonth != null; 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();
}
} }
@@ -567,4 +567,22 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
return this.createdDate; 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;
}
} }
@@ -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());
}
}
@@ -15,7 +15,7 @@ public interface SavingsAccountChargeReadPlatformService {
ChargeData retrieveSavingsAccountChargeTemplate(); ChargeData retrieveSavingsAccountChargeTemplate();
Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(Long savingsAccountId); Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(Long savingsAccountId, String status);
SavingsAccountChargeData retrieveSavingsAccountChargeDetails(Long savingsAccountChargeId, Long savingsAccountId); SavingsAccountChargeData retrieveSavingsAccountChargeDetails(Long savingsAccountChargeId, Long savingsAccountId);
@@ -71,6 +71,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
+ "sc.fee_on_month as feeOnMonth, " + "sc.fee_on_month as feeOnMonth, "
+ "sc.fee_on_day as feeOnDay, sc.fee_interval as feeInterval, " + "sc.fee_on_day as feeOnDay, sc.fee_interval as feeInterval, "
+ "sc.charge_calculation_enum as chargeCalculation, " + "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, " + "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.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+ "oc.internationalized_name_code as currencyNameCode from m_charge c " + "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 int chargeCalculation = rs.getInt("chargeCalculation");
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
final boolean penalty = rs.getBoolean("penalty"); final boolean penalty = rs.getBoolean("penalty");
final Boolean isActive = rs.getBoolean("isActive");
final LocalDate inactivationDate = JdbcSupport.getLocalDate(rs, "inactivationDate");
final Collection<ChargeData> chargeOptions = null; final Collection<ChargeData> chargeOptions = null;
return SavingsAccountChargeData.instance(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived, return SavingsAccountChargeData.instance(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived,
amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, 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 @Override
public Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(final Long loanId) { public Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(final Long loanId, final String status) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper(); 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=? " return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { loanId });
+ " 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 });
} }
private static final class SavingsAccountChargeDueMapper implements RowMapper<SavingsAccountAnnualFeeData> { private static final class SavingsAccountChargeDueMapper implements RowMapper<SavingsAccountAnnualFeeData> {
@@ -55,6 +55,8 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId); CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId);
CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
CommandProcessingResult inactivateCharge(Long savingsAccountId, Long savingsAccountChargeId);
void applyChargeDue(final Long savingsAccountChargeId, final Long accountId); void applyChargeDue(final Long savingsAccountChargeId, final Long accountId);
@@ -5,6 +5,7 @@
*/ */
package org.mifosplatform.portfolio.savings.service; 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.SAVINGS_ACCOUNT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@Service @Service
public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements SavingsAccountWritePlatformService { public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements SavingsAccountWritePlatformService {
@@ -938,4 +940,73 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); 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<ApiParameterError> 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<SavingsAccountTransaction> 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<SavingsAccountTransaction> chargePayments) {
if (!CollectionUtils.isEmpty(chargePayments)) { return chargePayments.get(chargePayments.size() - 1); }
return null;
}
} }