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>savingsaccounts/{accountId}/charges</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>
</tr>
@@ -553,6 +553,14 @@
<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>
<td><a href="#fdaccounts">Fixed Deposit Accounts</a></td>
<td>fixeddepositaccounts</td>
@@ -18833,6 +18841,11 @@ No Request Body:
<p>Example Requests:</p>
<div class=apiClick>savingsaccounts/1/charges</div>
<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>
<div class=apiClick>savingsaccounts/1/charges?fields=name,amountOrPercentage</div>
</div>
@@ -19318,7 +19331,7 @@ No Request Body:
<div class="method-description">
<h4>Pay a Savings account Charge</h4>
<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>
</div>
<div class="method-example">
@@ -19375,6 +19388,34 @@ No Request Body:
</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 -->
<!-- start of fixed deposit accounts api -->
@@ -32518,7 +32559,7 @@ No Request Body:
<td><a href="#savings_charges">Savings Account Charges</a></td>
<td>savingsaccounts/{accountId}/charges</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>
</tr>
@@ -32545,6 +32586,14 @@ No Request Body:
<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>
<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")
@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<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);
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<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);
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<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();
dates = (List) savingsChargeForPay.get("dueDate");
cal.set(Calendar.YEAR, (Integer) dates.get(0));
@@ -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<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");
}
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) {
this.actionName = "DELETE";
this.entityName = "SAVINGSACCOUNTCHARGE";
@@ -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());
}
@@ -206,14 +206,15 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
final Collection<ChargeData> 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;
}
@@ -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";
@@ -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<String> 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<String> mandatoryResponseParameters) {
final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo, final Set<String> mandatoryResponseParameters) {
Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsAccountChargeData> charges = null;
@@ -197,7 +200,7 @@ public class FixedDepositAccountsApiResource {
if (associationParameters.contains(SavingsApiConstants.charges)) {
mandatoryResponseParameters.add(SavingsApiConstants.charges);
final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId);
.retrieveSavingsAccountCharges(accountId, chargeStatus);
if (!CollectionUtils.isEmpty(currentCharges)) {
charges = currentCharges;
}
@@ -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<String> 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<String> mandatoryResponseParameters) {
final RecurringDepositAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final String chargeStatus,
final UriInfo uriInfo, final Set<String> mandatoryResponseParameters) {
Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsAccountChargeData> charges = null;
@@ -193,7 +196,7 @@ public class RecurringDepositAccountsApiResource {
if (associationParameters.contains(SavingsApiConstants.charges)) {
mandatoryResponseParameters.add(SavingsApiConstants.charges);
final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId);
.retrieveSavingsAccountCharges(accountId, chargeStatus);
if (!CollectionUtils.isEmpty(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.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<SavingsAccountChargeData> 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;
@@ -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<String> 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<String> mandatoryResponseParameters) {
final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo,
final Set<String> mandatoryResponseParameters) {
Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsAccountChargeData> charges = null;
@@ -171,7 +175,7 @@ public class SavingsAccountsApiResource {
if (associationParameters.contains(SavingsApiConstants.charges)) {
mandatoryResponseParameters.add(SavingsApiConstants.charges);
final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId);
.retrieveSavingsAccountCharges(accountId, chargeStatus);
if (!CollectionUtils.isEmpty(currentCharges)) {
charges = currentCharges;
}
@@ -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<ChargeData> chargeOptions;
@SuppressWarnings("unused")
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) {
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<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,
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<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.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() {
@@ -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;
@@ -769,7 +769,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
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<Long> {
public BigDecimal 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)
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<Long> {
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<Long> {
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<Long> {
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<Long> {
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();
}
}
@@ -567,4 +567,22 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
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();
Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(Long savingsAccountId);
Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(Long savingsAccountId, String status);
SavingsAccountChargeData retrieveSavingsAccountChargeDetails(Long savingsAccountChargeId, Long savingsAccountId);
@@ -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<ChargeData> 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<SavingsAccountChargeData> retrieveSavingsAccountCharges(final Long loanId) {
public Collection<SavingsAccountChargeData> 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<SavingsAccountAnnualFeeData> {
@@ -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);
@@ -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<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;
}
}