diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index cec952c08..27aebb90b 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -11062,6 +11062,11 @@ GET https://DomainName/api/v1/charges/template
"id": 1,
"code": "chargeAppliesTo.loan",
"value": "Loan"
+ },
+ {
+ "id": 2,
+ "code": "chargeAppliesTo.savings",
+ "value": "Savings"
}
],
"chargeTimeTypeOptions":
@@ -12324,7 +12329,7 @@ Content-Type: application/json
Optional Fields |
- | minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,withdrawalFeeForTransfers, annualFeeAmount, annualFeeOnMonthDay, monthDayFormat, paymentChannelToFundSourceMappings |
+ | minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,withdrawalFeeForTransfers, annualFeeAmount, annualFeeOnMonthDay, monthDayFormat, paymentChannelToFundSourceMappings, charges |
@@ -12344,7 +12349,8 @@ Request Body:
"interestPostingPeriodType":4,
"interestCalculationType": 1,
"interestCalculationDaysInYearType": "365",
- "accountingRule":"1"
+ "accountingRule":"1",
+ "charges":[{"id":"1"}]
}
@@ -12890,6 +12896,78 @@ Content-Type: application/json
"code": "savingsWithdrawalFeesType.percent.of.amount",
"value": "% of Amount"
}
+ ],
+ "chargeOptions": [
+ {
+ "id": 4,
+ "name": "Savings charge 1",
+ "active": true,
+ "penalty": false,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "amount": 200,
+ "chargeTimeType": {
+ "id": 2,
+ "code": "chargeTimeType.specifiedDueDate",
+ "value": "Specified due date"
+ },
+ "chargeAppliesTo": {
+ "id": 2,
+ "code": "chargeAppliesTo.savings",
+ "value": "Savings"
+ },
+ "chargeCalculationType": {
+ "id": 1,
+ "code": "chargeCalculationType.flat",
+ "value": "Flat"
+ },
+ "chargePaymentMode": {
+ "id": 0,
+ "code": "chargepaymentmode.regular",
+ "value": "chargepaymentmode.regular"
+ }
+ },
+ {
+ "id": 5,
+ "name": "Savings charge 2",
+ "active": true,
+ "penalty": false,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "amount": 300,
+ "chargeTimeType": {
+ "id": 2,
+ "code": "chargeTimeType.specifiedDueDate",
+ "value": "Specified due date"
+ },
+ "chargeAppliesTo": {
+ "id": 2,
+ "code": "chargeAppliesTo.savings",
+ "value": "Savings"
+ },
+ "chargeCalculationType": {
+ "id": 1,
+ "code": "chargeCalculationType.flat",
+ "value": "Flat"
+ },
+ "chargePaymentMode": {
+ "id": 0,
+ "code": "chargepaymentmode.regular",
+ "value": "chargepaymentmode.regular"
+ }
+ }
]
}
@@ -13395,6 +13473,7 @@ Request Body:
Associations are just extra pieces of data that you might or might not want to retrieve.
'all': Gets data related to all associations e.g. ?associations=all.
'transactions': Gets data related to transactions on the account e.g. ?associations=transactions
+
'charges':Savings Account charges data.
Example Requests :
savingsaccounts/1
@@ -13812,6 +13891,462 @@ No Request Body:
+
+
+
+
+
Savings Charges
+
Its typical for MFIs to add maintenance and operating charges. They can be either Fees or Penalties.
+
+
Savings Charges are instances of Charges and
+ represent either fees and penalties for savings products. Refer Charges
+ for documentation of the various properties of a charge, Only additional properties (
+ specific to the context of a Charge being associated with a Savings account) are
+ described here
+
+
+ Field Descriptions |
+
+
+ | amountPaid |
+
+
+ | The Total amount which has been paid for this Charge
+ |
+
+
+ | amountWaived |
+
+
+ | The Total amount that has been waived for this Charge
+ |
+
+
+ | amountWrittenOff |
+
+
+ | Total amount written off from this Charge
+ |
+
+
+ | amountOutstanding |
+
+
+ | The Total outstanding amount for this Charge
+ |
+
+
+
+
+
+
+
+
+
List Loan Charges
+
Example Requests:
+
savingsaccounts/1/charges
+
+
+
savingsaccounts/1/charges?fields=name,amountOrPercentage
+
+
+
+GET https://DomainName/api/v1/savingsaccounts/{accountId}/charges
+
+
+[
+ {
+ "id": 1,
+ "chargeId": 3,
+ "name": "Savings account maintenance fee",
+ "chargeTimeType": {
+ "id": 1,
+ "code": "chargeTimeType.specifiedDueDate",
+ "value": "Specified due date"
+ },
+ "chargeCalculationType": {
+ "id": 1,
+ "code": "chargeCalculationType.flat",
+ "value": "Flat"
+ },
+ "percentage": 0,
+ "amountPercentageAppliedTo": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "amount": 100,
+ "amountPaid": 0,
+ "amountWaived": 0,
+ "amountWrittenOff": 0,
+ "amountOutstanding": 100,
+ "amountOrPercentage": 100,
+ "penalty": false
+ },
+ {
+ "id": 2,
+ "chargeId": 4,
+ "name": "Pass book Fee",
+ "chargeTimeType": {
+ "id": 2,
+ "code": "chargeTimeType.specifiedDueDate",
+ "value": "Specified due date"
+ },
+ "dueDate": [
+ 2013,
+ 3,
+ 29
+ ],
+ "chargeCalculationType": {
+ "id": 1,
+ "code": "chargeCalculationType.flat",
+ "value": "Flat"
+ },
+ "percentage": 0,
+ "amountPercentageAppliedTo": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "amount": 100,
+ "amountPaid": 0,
+ "amountWaived": 0,
+ "amountWrittenOff": 0,
+ "amountOutstanding": 100,
+ "amountOrPercentage": 100,
+ "penalty": false
+ }
+]
+
+
+
+
+
+
+
+
Retrieve Savings Charges Template
+
This is a convenience resource. It can be useful when
+ building maintenance user interface screens for client
+ applications. The template data returned consists of any or all
+ of:
+
+ - Field Defaults
+ - Allowed Value Lists
+
+
+
Example Request:
+
savingsaccounts/1/charges/template
+
+
+
+GET https://DomainName/api/v1/savingsaccounts/{accountId}/charges/template
+
+
+{
+ "amountPaid": 0,
+ "amountWaived": 0,
+ "amountWrittenOff": 0,
+ "chargeOptions": [
+ {
+ "id": 2,
+ "name": "Passbook Fee",
+ "active": true,
+ "penalty": false,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "amount": 100,
+ "chargeTimeType": {
+ "id": 2,
+ "code": "chargeTimeType.specifiedDueDate",
+ "value": "Specified due date"
+ },
+ "chargeAppliesTo": {
+ "id": 1,
+ "code": "chargeAppliesTo.savings",
+ "value": "Savings"
+ },
+ "chargeCalculationType": {
+ "id": 1,
+ "code": "chargeCalculationType.flat",
+ "value": "Flat"
+ }
+ },
+ {
+ "id": 3,
+ "name": "Chequebook fee",
+ "active": true,
+ "penalty": true,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "amount": 1,
+ "chargeTimeType": {
+ "id": 2,
+ "code": "chargeTimeType.specifiedDueDate",
+ "value": "Specified due date"
+ },
+ "chargeAppliesTo": {
+ "id": 1,
+ "code": "chargeAppliesTo.savings",
+ "value": "Savings"
+ },
+ "chargeCalculationType": {
+ "id": 1,
+ "code": "chargeCalculationType.flat",
+ "value": "Flat"
+ }
+ }
+ ],
+ "penalty": false
+}
+
+
+
+
+
+
+
+
Retrieve a Savings account Charge
+
Example Requests:
+
/savingsaccounts/1/charges/5
+
+
+
/savingsaccounts/1/charges/5?fields=name,amountOrPercentage
+
+
+
+GET https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}
+
+
+{
+ "id": 1,
+ "chargeId": 1,
+ "name": "Passbook fee",
+ "chargeTimeType": {
+ "id": 1,
+ "code": "chargeTimeType.specifiedDueDate",
+ "value": "Specified due date"
+ },
+ "chargeCalculationType": {
+ "id": 1,
+ "code": "chargeCalculationType.flat",
+ "value": "Flat"
+ },
+ "percentage": 0,
+ "amountPercentageAppliedTo": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "amount": 100,
+ "amountPaid": 0,
+ "amountWaived": 0,
+ "amountWrittenOff": 0,
+ "amountOutstanding": 100,
+ "amountOrPercentage": 100,
+ "penalty": false
+}
+
+
+
+
+
+
+
Create a Savings account Charge
+
+
+ Mandatory Fields
+ for Savings account Charges |
+
+
+ | chargeId, amount, dueDate |
+
+
+
+
+
+POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges
+
+
+POST /savingsaccounts/1/charges
+Content-Type: application/json
+Request Body:
+{
+ "chargeId": "2",
+ "locale": "en",
+ "amount": "100",
+ "dateFormat": "dd MMMM yyyy",
+ "dueDate": "29 April 2013"
+}
+
+
+{
+ "officeId": 1,
+ "clientId": 1,
+ "savingsId": 1,
+ "resourceId": 6
+}
+
+
+
+
+
+
+
+
Update a Savings account Charge
+
Currently Savings account Charges may be updated only if the Savings account
+ is not yet approved.
+
+
+
+
+PUT https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}
+
+
+PUT savingsaccounts/1/charges/6
+Content-Type: application/json
+Request Body:
+{
+ "dateFormat": "dd MMMM yyyy",
+ "locale": "en",
+ "amount": "60",
+ "dueDate": "27 March 2013"
+}
+
+
+{
+ "officeId": 1,
+ "clientId": 1,
+ "savingsId": 1,
+ "resourceId": 6,
+ "changes": {
+ "dueDate": "27 March 2013",
+ "dateFormat": "dd MMMM yyyy",
+ "locale": "en",
+ "amount": 60.0
+ }
+}
+
+
+
+
+
+
+
+
Delete a Savings account Charge
+
+ Note:Currently, A Savings account Charge may only be removed from Savings that are not
+ yet approved.
+
+
+
+
+DELETE https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}
+
+
+DELETE savingsaccounts/1/charges/2
+Content-Type: application/json
+No Request Body:
+
+
+{
+ "officeId": 1,
+ "clientId": 1,
+ "savingsId": 1,
+ "resourceId": 2
+}
+
+
+
+
+
+
+
+
Pay a Savings account Charge
+
+ A charge will be paid when savings account is active and having sufficient balance.
+
+
+
+
+POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=paycharge
+
+
+POST savingsaccounts/1/charges/2?command=paycharge
+Content-Type: application/json
+Request Body:
+{
+ "locale": "en",
+ "amount": "60",
+}
+
+
+{
+ "officeId": 1,
+ "clientId": 1,
+ "savingsId": 1,
+ "resourceId": 2
+}
+
+
+
+
+
+
+
+
Waive off a Savings account Charge
+
+ Outstanding charge amount will be waived off.
+
+
+
+
+POST https://DomainName/api/v1/savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=waive
+
+
+POST savingsaccounts/1/charges/2?command=waive
+Content-Type: application/json
+No Request Body:
+
+
+{
+ "officeId": 1,
+ "clientId": 1,
+ "savingsId": 1,
+ "resourceId": 2
+}
+
+
+
+
+
@@ -19949,6 +20484,38 @@ GET https://DomainName/api/v1/search?query=000000111
|
|
+
+ | Savings Account Charges |
+ savingsaccounts/{accountId}/charges |
+ Add a Savings Account Charge |
+ |
+ |
+ |
+
+
+ |
+ savingsaccounts/{accountId}/charges/{savingsAccountChargeId} |
+ |
+ Retrieve a Savings Account Charge |
+ Modify a Savings Account Charge |
+ Delete a Savings Account Charge |
+
+
+ |
+ savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=paycharge |
+ Pay a Savings Account Charge |
+ |
+ |
+ |
+
+
+ |
+ savingsaccounts/{accountId}/charges/{savingsAccountChargeId}?command=waive |
+ Waive a Savings Account Charge |
+ |
+ |
+ |
+
| Account Transfers |
accounttransfers/ |
diff --git a/mifosng-db/migrations/core_db/V118__savings_charge.sql b/mifosng-db/migrations/core_db/V118__savings_charge.sql
new file mode 100644
index 000000000..83ea756a6
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V118__savings_charge.sql
@@ -0,0 +1,64 @@
+CREATE TABLE `m_savings_account_charge` (
+ `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+ `savings_account_id` BIGINT(20) NOT NULL,
+ `charge_id` BIGINT(20) NOT NULL,
+ `is_penalty` TINYINT(1) NOT NULL DEFAULT '0',
+ `charge_time_enum` SMALLINT(5) NOT NULL,
+ `due_for_collection_as_of_date` DATE NULL DEFAULT NULL,
+ `charge_calculation_enum` SMALLINT(5) NOT NULL,
+ `calculation_percentage` DECIMAL(19,6) NULL DEFAULT NULL,
+ `calculation_on_amount` DECIMAL(19,6) NULL DEFAULT NULL,
+ `amount` DECIMAL(19,6) NOT NULL,
+ `amount_paid_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+ `amount_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+ `amount_writtenoff_derived` DECIMAL(19,6) NULL DEFAULT NULL,
+ `amount_outstanding_derived` DECIMAL(19,6) NOT NULL DEFAULT '0.000000',
+ `is_paid_derived` TINYINT(1) NOT NULL DEFAULT '0',
+ `waived` TINYINT(1) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ INDEX `charge_id` (`charge_id`),
+ INDEX `m_savings_account_charge_ibfk_2` (`savings_account_id`),
+ CONSTRAINT `m_savings_account_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+ CONSTRAINT `m_savings_account_charge_ibfk_2` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `m_savings_product_charge` (
+ `savings_product_id` BIGINT(20) NOT NULL,
+ `charge_id` BIGINT(20) NOT NULL,
+ PRIMARY KEY (`savings_product_id`, `charge_id`),
+ INDEX `charge_id` (`charge_id`),
+ CONSTRAINT `m_savings_product_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
+ CONSTRAINT `m_savings_product_charge_ibfk_2` FOREIGN KEY (`savings_product_id`) REFERENCES `m_savings_product` (`id`)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'CREATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'UPDATE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'DELETE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'WAIVE_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'WAIVE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'WAIVE_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'WAIVE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'PAY_SAVINGSACCOUNTCHARGE', 'SAVINGSACCOUNTCHARGE', 'PAY', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ( 'portfolio', 'PAY_SAVINGSACCOUNTCHARGE_CHECKER', 'SAVINGSACCOUNTCHARGE', 'PAY', 0);
+
+
+
+CREATE TABLE `m_savings_account_charge_paid_by` (
+ `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+ `savings_account_transaction_id` BIGINT(20) NOT NULL,
+ `savings_account_charge_id` BIGINT(20) NOT NULL,
+ `amount` DECIMAL(19,6) NOT NULL,
+ PRIMARY KEY (`id`),
+ INDEX `FK__m_savings_account_transaction` (`savings_account_transaction_id`),
+ INDEX `FK__m_savings_account_charge` (`savings_account_charge_id`),
+ CONSTRAINT `FK__m_savings_account_charge` FOREIGN KEY (`savings_account_charge_id`) REFERENCES `m_savings_account_charge` (`id`),
+ CONSTRAINT `FK__m_savings_account_transaction` FOREIGN KEY (`savings_account_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
index 7d6ab3c03..033877b3b 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
@@ -445,7 +445,7 @@ public class CommandWrapper {
public boolean isSavingsAccountResource() {
return this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
-
+
public boolean isRejectionOfSavingsAccountApplication() {
return this.actionName.equalsIgnoreCase("REJECT") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
@@ -498,6 +498,30 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("ADJUSTTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
+ public boolean isSavingsAccountChargeResource() {
+ return this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
+ }
+
+ public boolean isAddSavingsAccountCharge() {
+ return this.actionName.equalsIgnoreCase("CREATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
+ }
+
+ public boolean isDeleteSavingsAccountCharge() {
+ return isDeleteOperation() && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
+ }
+
+ public boolean isUpdateSavingsAccountCharge() {
+ return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
+ }
+
+ public boolean isWaiveSavingsAccountCharge() {
+ return this.actionName.equalsIgnoreCase("WAIVE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
+ }
+
+ public boolean isPaySavingsAccountCharge() {
+ return this.actionName.equalsIgnoreCase("PAY") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
+ }
+
public boolean isCalendarResource() {
return this.entityName.equalsIgnoreCase("CALENDAR");
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
index 119540c25..15c2b3caf 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
@@ -941,6 +941,52 @@ public class CommandWrapperBuilder {
this.href = "/savingsaccounts/" + accountId + "?command=applyAnnualFees";
return this;
}
+
+ public CommandWrapperBuilder createSavingsAccountCharge(final Long savingsAccountId) {
+ this.actionName = "CREATE";
+ this.entityName = "SAVINGSACCOUNTCHARGE";
+ this.savingsId = savingsAccountId;
+ this.href = "/savingsaccounts/" + savingsAccountId + "/charges";
+ return this;
+ }
+
+ public CommandWrapperBuilder updateSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+ this.actionName = "UPDATE";
+ this.entityName = "SAVINGSACCOUNTCHARGE";
+ this.entityId = savingsAccountChargeId;
+ this.savingsId = savingsAccountId;
+ this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId;
+ return this;
+ }
+
+ public CommandWrapperBuilder waiveSavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+ this.actionName = "WAIVE";
+ this.entityName = "SAVINGSACCOUNTCHARGE";
+ this.entityId = savingsAccountChargeId;
+ this.savingsId = savingsAccountId;
+ this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId;
+ return this;
+
+ }
+
+ public CommandWrapperBuilder paySavingsAccountCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
+ this.actionName = "PAY";
+ 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";
+ this.entityId = savingsAccountChargeId;
+ this.savingsId = savingsAccountId;
+ this.href = "/savingsaccounts/" + savingsAccountId + "/charges/" + savingsAccountChargeId;
+ return this;
+ }
public CommandWrapperBuilder createCalendar(final String supportedEntityType, final Long supportedEntityId) {
this.actionName = "CREATE";
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
index 6fe400800..334a989f3 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
@@ -429,6 +429,20 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
+ } else if (wrapper.isSavingsAccountChargeResource()) {
+ if (wrapper.isAddSavingsAccountCharge()) {
+ handler = this.applicationContext.getBean("addSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isDeleteSavingsAccountCharge()) {
+ handler = this.applicationContext.getBean("deleteSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isUpdateSavingsAccountCharge()) {
+ handler = this.applicationContext.getBean("updateSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isWaiveSavingsAccountCharge()) {
+ handler = this.applicationContext.getBean("waiveSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isPaySavingsAccountCharge()) {
+ handler = this.applicationContext.getBean("paySavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
+ } else {
+ throw new UnsupportedCommandException(wrapper.commandName());
+ }
} else if (wrapper.isCalendarResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createCalendarCommandHandler", NewCommandSourceHandler.class);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java
index b12ab1d0d..cfba9aecc 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java
@@ -10,9 +10,11 @@ import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
+import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
+import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
/**
* Immutable data object for charge data.
@@ -125,4 +127,21 @@ public class ChargeData implements Comparable, Serializable {
return LoanChargeData.newLoanChargeDetails(this.id, this.name, this.currency, this.amount, percentage, this.chargeTimeType,
this.chargeCalculationType, this.penalty,this.chargePaymentMode);
}
+
+ public SavingsAccountChargeData toSavingsAccountChargeData(){
+
+ final Long savingsChargeId = null;
+ final BigDecimal amountPaid = BigDecimal.ZERO;
+ final BigDecimal amountWaived = BigDecimal.ZERO;
+ final BigDecimal amountWrittenOff = BigDecimal.ZERO;
+ final BigDecimal amountOutstanding = BigDecimal.ZERO;
+ final BigDecimal percentage = BigDecimal.ZERO;
+ final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
+ final Collection chargeOptions = null;
+ final LocalDate dueAsOfDate = null;
+
+ return SavingsAccountChargeData.instance(savingsChargeId, this.id, this.name, this.currency, this.amount, amountPaid, amountWaived,
+ amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, percentage,
+ amountPercentageAppliedTo, chargeOptions, this.penalty);
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java
index 85c64b5bc..4ce9e5914 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java
@@ -6,7 +6,9 @@
package org.mifosplatform.portfolio.charge.domain;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import javax.persistence.Column;
@@ -17,10 +19,14 @@ import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
+import org.mifosplatform.infrastructure.core.data.ApiParameterError;
+import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
+import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.exception.ChargeDueAtDisbursementCannotBePenaltyException;
+import org.mifosplatform.portfolio.charge.exception.ChargeParameterUpdateNotSupportedException;
import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.springframework.data.jpa.domain.AbstractPersistable;
@@ -93,7 +99,33 @@ public class Charge extends AbstractPersistable {
this.penalty = penalty;
this.active = active;
this.chargePaymentMode = paymentMode.getValue();
- if (penalty && chargeTime.isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); }
+
+ final List dataValidationErrors = new ArrayList();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
+
+ if (isSavingsCharge()) {
+
+ if (!isAllowedSavingsChargeTime()) {
+ baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
+ .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
+ }
+
+ if (!isAllowedSavingsChargeCalculationType()) {
+ baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
+ .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
+ }
+
+ } else if (isLoanCharge()) {
+
+ if (penalty && chargeTime.isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); }
+
+ if (!isAllowedLoanChargeTime()) {
+ baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
+ .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
+ }
+ }
+
+ if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@Override
@@ -145,12 +177,35 @@ public class Charge extends AbstractPersistable {
public boolean isDeleted() {
return deleted;
}
+
+ public boolean isLoanCharge(){
+ return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isLoanCharge();
+ }
+
+ public boolean isAllowedLoanChargeTime(){
+ return ChargeTimeType.fromInt(this.chargeTime).isAllowedLoanChargeTime();
+ }
+
+ public boolean isSavingsCharge(){
+ return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isSavingsCharge();
+ }
+
+ public boolean isAllowedSavingsChargeTime(){
+ return ChargeTimeType.fromInt(this.chargeTime).isAllowedSavingsChargeTime();
+ }
+ public boolean isAllowedSavingsChargeCalculationType(){
+ return ChargeCalculationType.fromInt(this.chargeCalculation).isAllowedSavingsChargeCalculationType();
+ }
+
public Map update(final JsonCommand command) {
final Map actualChanges = new LinkedHashMap(7);
final String localeAsInput = command.locale();
+
+ final List dataValidationErrors = new ArrayList();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
final String nameParamName = "name";
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
@@ -180,14 +235,31 @@ public class Charge extends AbstractPersistable {
actualChanges.put(chargeTimeParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.chargeTime = ChargeTimeType.fromInt(newValue).getValue();
+
+ if (isSavingsCharge()) {
+ if (!isAllowedSavingsChargeTime()) {
+ baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
+ .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
+ }
+ } else if (isLoanCharge()) {
+ if (!isAllowedLoanChargeTime()) {
+ baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
+ .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
+ }
+ }
+
}
final String chargeAppliesToParamName = "chargeAppliesTo";
if (command.isChangeInIntegerParameterNamed(chargeAppliesToParamName, this.chargeAppliesTo)) {
- final Integer newValue = command.integerValueOfParameterNamed(chargeAppliesToParamName);
+ /*final Integer newValue = command.integerValueOfParameterNamed(chargeAppliesToParamName);
actualChanges.put(chargeAppliesToParamName, newValue);
actualChanges.put("locale", localeAsInput);
- this.chargeAppliesTo = ChargeAppliesTo.fromInt(newValue).getValue();
+ this.chargeAppliesTo = ChargeAppliesTo.fromInt(newValue).getValue();*/
+
+ //AA: Do not allow to change chargeAppliesTo.
+ final String errorMessage = "Update of Charge applies to is not supported";
+ throw new ChargeParameterUpdateNotSupportedException("charge.applies.to", errorMessage);
}
final String chargeCalculationParamName = "chargeCalculationType";
@@ -196,6 +268,13 @@ public class Charge extends AbstractPersistable {
actualChanges.put(chargeCalculationParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.chargeCalculation = ChargeCalculationType.fromInt(newValue).getValue();
+
+ if (isSavingsCharge()) {
+ if (!isAllowedSavingsChargeCalculationType()) {
+ baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
+ .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
+ }
+ }
}
final String paymentModeParamName = "chargePaymentMode";
@@ -223,7 +302,9 @@ public class Charge extends AbstractPersistable {
if (this.penalty && ChargeTimeType.fromInt(this.chargeTime).isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(
name); }
-
+
+ if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
return actualChanges;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeAppliesTo.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeAppliesTo.java
index cc6f5042e..85c056c0e 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeAppliesTo.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeAppliesTo.java
@@ -8,7 +8,8 @@ package org.mifosplatform.portfolio.charge.domain;
public enum ChargeAppliesTo {
INVALID(0, "chargeAppliesTo.invalid"), //
- LOAN(1, "chargeAppliesTo.loan");
+ LOAN(1, "chargeAppliesTo.loan"),
+ SAVINGS(2, "chargeAppliesTo.savings");
private final Integer value;
private final String code;
@@ -26,16 +27,35 @@ public enum ChargeAppliesTo {
return code;
}
+ public static int minValue(){
+ return 1;
+ }
+
+ public static int maxValue(){
+ return 2;//modify whenever a new value added
+ }
+
public static ChargeAppliesTo fromInt(final Integer chargeAppliesTo) {
ChargeAppliesTo chargeAppliesToType = ChargeAppliesTo.INVALID;
switch (chargeAppliesTo) {
case 1:
chargeAppliesToType = LOAN;
break;
+ case 2:
+ chargeAppliesToType = SAVINGS;
+ break;
default:
chargeAppliesToType = INVALID;
break;
}
return chargeAppliesToType;
}
+
+ public boolean isLoanCharge(){
+ return this.value.equals(ChargeAppliesTo.LOAN.getValue());
+ }
+
+ public boolean isSavingsCharge(){
+ return this.value.equals(ChargeAppliesTo.SAVINGS.getValue());
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java
index d53232968..fbd70d5bb 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java
@@ -47,4 +47,16 @@ public enum ChargeCalculationType {
}
return chargeCalculationType;
}
+
+ public boolean isPercentageOfAmount(){
+ return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue());
+ }
+
+ public boolean isFlat(){
+ return this.value.equals(ChargeCalculationType.FLAT.getValue());
+ }
+
+ public boolean isAllowedSavingsChargeCalculationType(){
+ return this.isFlat();
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java
index ed38e0e81..7b157e749 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java
@@ -5,11 +5,14 @@
*/
package org.mifosplatform.portfolio.charge.domain;
+
public enum ChargeTimeType {
INVALID(0, "chargeTimeType.invalid"), //
DISBURSEMENT(1, "chargeTimeType.disbursement"), //
- SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate");
+ SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"),
+ MONTHLY(3, "chargeTimeType.monthly"),
+ YEARLY(4, "chargeTimeType.yearly");
private final Integer value;
private final String code;
@@ -27,6 +30,14 @@ public enum ChargeTimeType {
return code;
}
+ public static int minValue(){
+ return 1;
+ }
+
+ public static int maxValue(){
+ return 4;
+ }
+
public static ChargeTimeType fromInt(final Integer chargeTime) {
ChargeTimeType chargeTimeType = ChargeTimeType.INVALID;
switch (chargeTime) {
@@ -36,6 +47,12 @@ public enum ChargeTimeType {
case 2:
chargeTimeType = SPECIFIED_DUE_DATE;
break;
+ case 3:
+ chargeTimeType = MONTHLY;
+ break;
+ case 4:
+ chargeTimeType = YEARLY;
+ break;
default:
chargeTimeType = INVALID;
break;
@@ -46,4 +63,24 @@ public enum ChargeTimeType {
public boolean isTimeOfDisbursement() {
return ChargeTimeType.DISBURSEMENT.getValue().equals(this.value);
}
+
+ public boolean isOnSpecifiedDueDate() {
+ return this.value.equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue());
+ }
+
+ public boolean isMonthly(){
+ return this.value.equals(ChargeTimeType.MONTHLY.getValue());
+ }
+
+ public boolean isYearly(){
+ return this.value.equals(ChargeTimeType.YEARLY.getValue());
+ }
+
+ public boolean isAllowedLoanChargeTime(){
+ return this.isTimeOfDisbursement() || this.isOnSpecifiedDueDate();
+ }
+
+ public boolean isAllowedSavingsChargeTime(){
+ return this.isOnSpecifiedDueDate() || this.isMonthly() || this.isYearly();
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/ChargeCannotBeAppliedToException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/ChargeCannotBeAppliedToException.java
new file mode 100644
index 000000000..10439f943
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/ChargeCannotBeAppliedToException.java
@@ -0,0 +1,13 @@
+package org.mifosplatform.portfolio.charge.exception;
+
+import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+
+public class ChargeCannotBeAppliedToException extends AbstractPlatformDomainRuleException {
+
+ public ChargeCannotBeAppliedToException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
+
+ super("error.msg.charge.cannot.be.applied.to" + postFix, defaultUserMessage, defaultUserMessageArgs);
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/ChargeParameterUpdateNotSupportedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/ChargeParameterUpdateNotSupportedException.java
new file mode 100644
index 000000000..b6a23b85d
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/ChargeParameterUpdateNotSupportedException.java
@@ -0,0 +1,18 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.charge.exception;
+
+import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when Calendar resources are not found.
+ */
+public class ChargeParameterUpdateNotSupportedException extends AbstractPlatformDomainRuleException {
+
+ public ChargeParameterUpdateNotSupportedException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
+ super("error.msg.charge.update.of." + postFix + ".is.not.supported", defaultUserMessage, defaultUserMessageArgs);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeCannotBeWaivedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeCannotBeWaivedException.java
new file mode 100644
index 000000000..2cf92c50f
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeCannotBeWaivedException.java
@@ -0,0 +1,43 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.charge.exception;
+
+import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SavingsAccountChargeCannotBeWaivedException extends AbstractPlatformDomainRuleException {
+
+ /*** enum of reasons of why Savings Account Charge cannot be waived **/
+ public static enum SAVINGS_ACCOUNT_CHARGE_CANNOT_BE_WAIVED_REASON {
+ ALREADY_PAID, ALREADY_WAIVED, SAVINGS_ACCOUNT_NOT_ACTIVE, SAVINGS_ACCOUNT_CLOSED;
+
+ public String errorMessage() {
+ if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+ return "This savings account charge has been completely paid";
+ } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+ return "This savings account charge has already been waived";
+ } else if (name().toString().equalsIgnoreCase("SAVINGS_ACCOUNT_NOT_ACTIVE")) {
+ return "This savings account charge cannot be waived as the Savings account associated with it is not active";
+ } else if (name().toString().equalsIgnoreCase("SAVINGS_ACCOUNT_CLOSED")) { return "This savings account charge cannot be waived as the Savings account associated with it is closed"; }
+ return name().toString();
+ }
+
+ public String errorCode() {
+ if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+ return "error.msg.savings.account.charge.already.paid";
+ } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+ return "error.msg.savings.account.charge.already.waived";
+ } else if (name().toString().equalsIgnoreCase("SAVINGS_ACCOUNT_NOT_ACTIVE")) {
+ return "error.msg.savings.account.charge.associated.savings.account.not.active";
+ } else if (name().toString().equalsIgnoreCase("SAVINGS_ACCOUNT_CLOSED")) { return "error.msg.savings.account.charge.associated.savings.account.closed"; }
+ return name().toString();
+ }
+ }
+
+ public SavingsAccountChargeCannotBeWaivedException(final SAVINGS_ACCOUNT_CHARGE_CANNOT_BE_WAIVED_REASON reason,
+ final Long savingsAccountChargeId) {
+ super(reason.errorCode(), reason.errorMessage(), savingsAccountChargeId);
+ }
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeNotFoundException.java
new file mode 100644
index 000000000..b5a3ff80d
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeNotFoundException.java
@@ -0,0 +1,25 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.charge.exception;
+
+import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when savings account charge does not
+ * exist.
+ */
+public class SavingsAccountChargeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public SavingsAccountChargeNotFoundException(final Long id) {
+ super("error.msg.savings.account.charge.id.invalid", "Savings Account charge with identifier " + id + " does not exist", id);
+ }
+
+ public SavingsAccountChargeNotFoundException(final Long id, final Long savingsAccountId) {
+ super("error.msg.savings.account.charge.id.invalid.for.given.savings.account", "Savings Account charge with identifier " + id + " does not exist for Savings Account " + savingsAccountId,
+ id, savingsAccountId);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeWithoutMandatoryFieldException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeWithoutMandatoryFieldException.java
new file mode 100644
index 000000000..b9b59e0cd
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/SavingsAccountChargeWithoutMandatoryFieldException.java
@@ -0,0 +1,19 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.charge.exception;
+
+import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown when currency mismatch occurs
+ */
+public class SavingsAccountChargeWithoutMandatoryFieldException extends AbstractPlatformDomainRuleException {
+
+ public SavingsAccountChargeWithoutMandatoryFieldException(final String entity, final String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
+ super("error.msg."+entity+"."+postFix+".cannot.be.blank", defaultUserMessage, defaultUserMessageArgs);
+ }
+
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
index 800b5426d..2eb5fb4fb 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
@@ -21,6 +21,8 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
+import org.mifosplatform.portfolio.charge.domain.ChargeAppliesTo;
+import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -65,10 +67,10 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
final Integer chargeAppliesTo = fromApiJsonHelper.extractIntegerNamed("chargeAppliesTo", element, Locale.getDefault());
- baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull().inMinMaxRange(1, 1);
+ baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull().inMinMaxRange(ChargeAppliesTo.minValue(), ChargeAppliesTo.maxValue());
final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", element, Locale.getDefault());
- baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull().inMinMaxRange(1, 2);
+ baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull().inMinMaxRange(ChargeTimeType.minValue(), ChargeTimeType.maxValue());
final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 4);
@@ -116,12 +118,12 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
if (fromApiJsonHelper.parameterExists("chargeAppliesTo", element)) {
final Integer chargeAppliesTo = fromApiJsonHelper.extractIntegerNamed("chargeAppliesTo", element, Locale.getDefault());
- baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull().inMinMaxRange(1, 1);
+ baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull().inMinMaxRange(ChargeAppliesTo.minValue(), ChargeAppliesTo.maxValue());
}
if (fromApiJsonHelper.parameterExists("chargeTimeType", element)) {
final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", element, Locale.getDefault());
- baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull().inMinMaxRange(1, 2);
+ baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull().inMinMaxRange(ChargeTimeType.minValue(), ChargeTimeType.maxValue());
}
if (fromApiJsonHelper.parameterExists("chargeCalculationType", element)) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java
index 27368266b..c25906054 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java
@@ -5,11 +5,10 @@
*/
package org.mifosplatform.portfolio.charge.service;
-import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeAppliesTo;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeCalculationType;
-import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeTimeType;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargePaymentMode;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -35,12 +34,25 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
@Override
public List retrieveApplicableToTypes() {
- return Arrays.asList(chargeAppliesTo(ChargeAppliesTo.LOAN));
+ List chargeAppliesToTypes = new ArrayList();
+ for (ChargeAppliesTo chargeAppliesTo : ChargeAppliesTo.values()) {
+ if(ChargeAppliesTo.INVALID.equals(chargeAppliesTo)) continue;
+ chargeAppliesToTypes.add(ChargeEnumerations.chargeAppliesTo(chargeAppliesTo));
+ }
+ return chargeAppliesToTypes;
}
@Override
public List retrieveCollectionTimeTypes() {
- return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE));
+ List chargeTimeTypes = new ArrayList();
+ for (ChargeTimeType chargeTimeType : ChargeTimeType.values()) {
+ if (ChargeTimeType.INVALID.equals(chargeTimeType)
+ || ChargeTimeType.MONTHLY.equals(chargeTimeType) // To be implemented for Savings
+ || ChargeTimeType.YEARLY.equals(chargeTimeType)) // To be implemented for Savings
+ continue;
+ chargeTimeTypes.add(ChargeEnumerations.chargeTimeType(chargeTimeType));
+ }
+ return chargeTimeTypes;
}
@Override
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java
index a35166de6..899179497 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java
@@ -28,6 +28,14 @@ public class ChargeEnumerations {
optionData = new EnumOptionData(ChargeTimeType.SPECIFIED_DUE_DATE.getValue().longValue(),
ChargeTimeType.SPECIFIED_DUE_DATE.getCode(), "Specified due date");
break;
+ case MONTHLY:
+ optionData = new EnumOptionData(ChargeTimeType.MONTHLY.getValue().longValue(),
+ ChargeTimeType.MONTHLY.getCode(), "Monthly");
+ break;
+ case YEARLY:
+ optionData = new EnumOptionData(ChargeTimeType.YEARLY.getValue().longValue(),
+ ChargeTimeType.YEARLY.getCode(), "Yearly");
+ break;
default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break;
@@ -45,6 +53,9 @@ public class ChargeEnumerations {
case LOAN:
optionData = new EnumOptionData(ChargeAppliesTo.LOAN.getValue().longValue(), ChargeAppliesTo.LOAN.getCode(), "Loan");
break;
+ case SAVINGS:
+ optionData = new EnumOptionData(ChargeAppliesTo.SAVINGS.getValue().longValue(), ChargeAppliesTo.SAVINGS.getCode(), "Savings");
+ break;
default:
optionData = new EnumOptionData(ChargeAppliesTo.INVALID.getValue().longValue(), ChargeAppliesTo.INVALID.getCode(),
"Invalid");
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformService.java
index 802a54779..d373160a2 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformService.java
@@ -22,4 +22,10 @@ public interface ChargeReadPlatformService {
Collection retrieveLoanApplicablePenalties();
Collection retrieveLoanProductCharges(Long loanProductId);
+
+ Collection retrieveSavingsAccountApplicableCharges(boolean feeChargesOnly);
+
+ Collection retrieveSavingsAccountApplicablePenalties();
+
+ Collection retrieveSavingsProductCharges(Long savingsProductId);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
index 566b5cc5c..5d2447cd5 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
@@ -141,6 +141,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
public String loanProductChargeSchema() {
return chargeSchema() + " join m_product_loan_charge plc on plc.charge_id = c.id";
}
+
+ public String savingsProductChargeSchema() {
+ return chargeSchema() + " join m_savings_product_charge spc on spc.charge_id = c.id";
+ }
@Override
public ChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
@@ -179,4 +183,42 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
}
}
+ @Override
+ public Collection retrieveSavingsAccountApplicableCharges(boolean feeChargesOnly) {
+ this.context.authenticatedUser();
+
+ final ChargeMapper rm = new ChargeMapper();
+
+ String sql = "select " + rm.chargeSchema()
+ + " where c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=? order by c.name ";
+ if (feeChargesOnly) {
+ sql = "select " + rm.chargeSchema()
+ + " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=0 and c.charge_applies_to_enum=? order by c.name ";
+ }
+
+ return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.SAVINGS.getValue() });
+ }
+
+ @Override
+ public Collection retrieveSavingsAccountApplicablePenalties() {
+ this.context.authenticatedUser();
+
+ final ChargeMapper rm = new ChargeMapper();
+
+ String sql = "select " + rm.chargeSchema()
+ + " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=1 and c.charge_applies_to_enum=? order by c.name ";
+ return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.SAVINGS.getValue() });
+ }
+
+ @Override
+ public Collection retrieveSavingsProductCharges(Long savingsProductId) {
+ this.context.authenticatedUser();
+
+ final ChargeMapper rm = new ChargeMapper();
+
+ final String sql = "select " + rm.savingsProductChargeSchema() + " where c.is_deleted=0 and c.is_active=1 and spc.savings_product_id=?";
+
+ return this.jdbcTemplate.query(sql, rm, new Object[] { savingsProductId });
+ }
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java
index 6141da8de..afda6a0a4 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java
@@ -19,6 +19,8 @@ public enum SavingsAccountTransactionType {
INTEREST_POSTING(3, "savingsAccountTransactionType.interestPosting"), //
WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"), //
ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee"), //
+ WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharges"),
+ APPLY_CHARGES(7, "savingsAccountTransactionType.applyCharges"),
INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), //
APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), //
WITHDRAW_TRANSFER(14, "savingsAccountTransactionType.withdrawTransfer"), //
@@ -44,37 +46,43 @@ public enum SavingsAccountTransactionType {
if (transactionType == null) { return SavingsAccountTransactionType.INVALID; }
- SavingsAccountTransactionType loanTransactionType = SavingsAccountTransactionType.INVALID;
+ SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.INVALID;
switch (transactionType) {
case 1:
- loanTransactionType = SavingsAccountTransactionType.DEPOSIT;
+ savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT;
break;
case 2:
- loanTransactionType = SavingsAccountTransactionType.WITHDRAWAL;
+ savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAWAL;
break;
case 3:
- loanTransactionType = SavingsAccountTransactionType.INTEREST_POSTING;
+ savingsAccountTransactionType = SavingsAccountTransactionType.INTEREST_POSTING;
break;
case 4:
- loanTransactionType = SavingsAccountTransactionType.WITHDRAWAL_FEE;
+ savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAWAL_FEE;
break;
case 5:
- loanTransactionType = SavingsAccountTransactionType.ANNUAL_FEE;
+ savingsAccountTransactionType = SavingsAccountTransactionType.ANNUAL_FEE;
+ break;
+ case 6:
+ savingsAccountTransactionType = SavingsAccountTransactionType.WAIVE_CHARGES;
+ break;
+ case 7:
+ savingsAccountTransactionType = SavingsAccountTransactionType.APPLY_CHARGES;
break;
case 12:
- loanTransactionType = SavingsAccountTransactionType.INITIATE_TRANSFER;
+ savingsAccountTransactionType = SavingsAccountTransactionType.INITIATE_TRANSFER;
break;
case 13:
- loanTransactionType = SavingsAccountTransactionType.APPROVE_TRANSFER;
+ savingsAccountTransactionType = SavingsAccountTransactionType.APPROVE_TRANSFER;
break;
case 14:
- loanTransactionType = SavingsAccountTransactionType.WITHDRAW_TRANSFER;
+ savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAW_TRANSFER;
break;
case 15:
- loanTransactionType = SavingsAccountTransactionType.REJECT_TRANSFER;
+ savingsAccountTransactionType = SavingsAccountTransactionType.REJECT_TRANSFER;
break;
}
- return loanTransactionType;
+ return savingsAccountTransactionType;
}
public boolean isDeposit() {
@@ -96,9 +104,17 @@ public enum SavingsAccountTransactionType {
public boolean isAnnualFee() {
return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue());
}
+
+ public boolean isCharge() {
+ return this.value.equals(SavingsAccountTransactionType.APPLY_CHARGES.getValue());
+ }
+
+ public boolean isWaiveCharge() {
+ return this.value.equals(SavingsAccountTransactionType.WAIVE_CHARGES.getValue());
+ }
public boolean isDebit() {
- return isWithdrawal() || isWithdrawalFee() || isAnnualFee();
+ return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isCharge();
}
public boolean isCredit() {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java
index fea9326a2..5fb159e92 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java
@@ -18,6 +18,8 @@ public class SavingsApiConstants {
public static final String SAVINGS_PRODUCT_RESOURCE_NAME = "savingsproduct";
public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount";
public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction";
+ public static final String SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME = "savingsaccountcharge";
+
// actions
public static String summitalAction = ".summital";
@@ -32,10 +34,13 @@ public class SavingsApiConstants {
public static String applyAnnualFeeTransactionAction = ".applyannualfee";
public static String adjustTransactionAction = ".adjusttransaction";
public static String closeAction = ".close";
+ public static String payChargeTransactionAction = ".paycharge";
// command
public static String COMMAND_UNDO_TRANSACTION = "undo";
public static String COMMAND_ADJUST_TRANSACTION = "modify";
+ public static String COMMAND_WAIVE_CHARGE= "waive";
+ public static String COMMAND_PAY_CHARGE= "paycharge";
// general
public static final String localeParamName = "locale";
@@ -93,6 +98,26 @@ public class SavingsApiConstants {
public static final String runningBalanceParamName = "runningBalance";
public static final String reversedParamName = "reversed";
public static final String dateParamName = "date";
+
+ //charges parameters
+ public static final String chargeIdParamName = "chargeId";
+ public static final String chargesParamName = "charges";
+ public static final String savingsAccountChargeIdParamName = "savingsAccountChargeId";
+ public static final String chargeNameParamName = "name";
+ public static final String penaltyParamName = "penalty";
+ public static final String chargeTimeTypeParamName = "chargeTimeType";
+ public static final String dueAsOfDateParamName = "dueDate";
+ public static final String chargeCalculationTypeParamName = "chargeCalculationType";
+ public static final String percentageParamName = "percentage";
+ public static final String amountPercentageAppliedToParamName = "amountPercentageAppliedTo";
+ public static final String currencyParamName = "currency";
+ public static final String amountWaivedParamName = "amountWaived";
+ public static final String amountWrittenOffParamName = "amountWrittenOff";
+ public static final String amountOutstandingParamName = "amountOutstanding";
+ public static final String amountOrPercentageParamName = "amountOrPercentage";
+ public static final String amountParamName = "amount";
+ public static final String amountPaidParamName = "amountPaid";
+ public static final String chargeOptionsParamName = "chargeOptions";
public static final String noteParamName = "note";
@@ -102,7 +127,8 @@ public class SavingsApiConstants {
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName,
- annualFeeOnMonthDayParamName, accountingRuleParamName, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
+ annualFeeOnMonthDayParamName, accountingRuleParamName, chargesParamName,
+ SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(),
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(),
@@ -128,7 +154,7 @@ public class SavingsApiConstants {
interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName,
lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
- withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName));
+ withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, chargesParamName));
/**
* These parameters will match the class level parameters of
@@ -158,4 +184,13 @@ public class SavingsApiConstants {
public static final Set SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName,
dateFormatParamName, closedOnDateParamName, noteParamName));
+
+ public static final Set SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(chargeIdParamName,
+ savingsAccountChargeIdParamName, chargeNameParamName, penaltyParamName, chargeTimeTypeParamName, dueAsOfDateParamName,
+ chargeCalculationTypeParamName, percentageParamName, amountPercentageAppliedToParamName, currencyParamName,
+ amountWaivedParamName, amountWrittenOffParamName, amountOutstandingParamName, amountOrPercentageParamName, amountParamName, amountPaidParamName,
+ chargeOptionsParamName));
+
+ public static final Set SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(
+ chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java
new file mode 100644
index 000000000..8c95bdafe
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java
@@ -0,0 +1,198 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.savings.api;
+
+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_WAIVE_CHARGE;
+import static org.mifosplatform.portfolio.savings.SavingsApiConstants.COMMAND_PAY_CHARGE;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.mifosplatform.commands.domain.CommandWrapper;
+import org.mifosplatform.commands.service.CommandWrapperBuilder;
+import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
+import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
+import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
+import org.mifosplatform.portfolio.charge.data.ChargeData;
+import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
+import org.mifosplatform.portfolio.savings.SavingsApiConstants;
+import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
+import org.mifosplatform.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/savingsaccounts/{savingsAccountId}/charges")
+@Component
+@Scope("singleton")
+public class SavingsAccountChargesApiResource {
+
+ private final PlatformSecurityContext context;
+ private final ChargeReadPlatformService chargeReadPlatformService;
+ private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
+ private final DefaultToApiJsonSerializer toApiJsonSerializer;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+ @Autowired
+ public SavingsAccountChargesApiResource(final PlatformSecurityContext context,
+ final ChargeReadPlatformService chargeReadPlatformService,
+ final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService,
+ final DefaultToApiJsonSerializer toApiJsonSerializer,
+ final ApiRequestParameterHelper apiRequestParameterHelper,
+ final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+ this.context = context;
+ this.chargeReadPlatformService = chargeReadPlatformService;
+ this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ }
+
+ private boolean is(final String commandParam, final String commandValue) {
+ return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+ }
+
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveAllSavingsAccountCharges(@PathParam("savingsAccountId") final Long savingsAccountId,
+ @Context final UriInfo uriInfo) {
+
+ context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+ final Collection savingsAccountCharges = this.savingsAccountChargeReadPlatformService
+ .retrieveSavingsAccountCharges(savingsAccountId);
+
+ final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, savingsAccountCharges,
+ SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS);
+ }
+
+ @GET
+ @Path("template")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveTemplate(@Context final UriInfo uriInfo) {
+
+ context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+ final boolean feeChargesOnly = false;
+ final Collection chargeOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicableCharges(feeChargesOnly);
+ final SavingsAccountChargeData savingsAccountChargeTemplate = SavingsAccountChargeData.template(chargeOptions);
+
+ final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, savingsAccountChargeTemplate, SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS);
+ }
+
+ @GET
+ @Path("{savingsAccountChargeId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
+ @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, @Context final UriInfo uriInfo) {
+
+ context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
+
+ final SavingsAccountChargeData savingsAccountCharge = this.savingsAccountChargeReadPlatformService
+ .retrieveSavingsAccountChargeDetails(savingsAccountChargeId, savingsAccountId);
+
+ final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, savingsAccountCharge, SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS);
+ }
+
+ @POST
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String addSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId, final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().createSavingsAccountCharge(savingsAccountId)
+ .withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @PUT
+ @Path("{savingsAccountChargeId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String updateSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
+ @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder()
+ .updateSavingsAccountCharge(savingsAccountId, savingsAccountChargeId).withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @POST
+ @Path("{savingsAccountChargeId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String waiveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
+ @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, @QueryParam("command") final String commandParam,
+ final String apiRequestBodyAsJson) {
+
+ String json = "";
+ if (is(commandParam, COMMAND_WAIVE_CHARGE)) {
+ final CommandWrapper commandRequest = new CommandWrapperBuilder()
+ .waiveSavingsAccountCharge(savingsAccountId, savingsAccountChargeId).withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ json = this.toApiJsonSerializer.serialize(result);
+ } else if (is(commandParam, COMMAND_PAY_CHARGE)) {
+ final CommandWrapper commandRequest = new CommandWrapperBuilder()
+ .paySavingsAccountCharge(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);
+ }
+
+ return json;
+ }
+
+ @DELETE
+ @Path("{savingsAccountChargeId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String deleteSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
+ @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSavingsAccountCharge(savingsAccountId,
+ savingsAccountChargeId).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java
index a1074510a..a53ccc6af 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java
@@ -38,8 +38,10 @@ import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
+import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData;
+import org.mifosplatform.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
@@ -56,17 +58,19 @@ public class SavingsAccountsApiResource {
private final DefaultToApiJsonSerializer toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
@Autowired
public SavingsAccountsApiResource(final SavingsAccountReadPlatformService savingsAccountReadPlatformService,
final PlatformSecurityContext context, final DefaultToApiJsonSerializer toApiJsonSerializer,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
- final ApiRequestParameterHelper apiRequestParameterHelper) {
+ final ApiRequestParameterHelper apiRequestParameterHelper, final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService) {
this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
this.context = context;
this.toApiJsonSerializer = toApiJsonSerializer;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
}
@GET
@@ -143,12 +147,13 @@ public class SavingsAccountsApiResource {
final boolean staffInSelectedOfficeOnly, final UriInfo uriInfo, final Set mandatoryResponseParameters) {
Collection transactions = null;
+ Collection charges = null;
final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) {
- associationParameters.addAll(Arrays.asList("transactions"));
+ associationParameters.addAll(Arrays.asList("transactions", "charges"));
}
if (associationParameters.contains("transactions")) {
@@ -159,6 +164,15 @@ public class SavingsAccountsApiResource {
transactions = currentTransactions;
}
}
+
+ if (associationParameters.contains("charges")) {
+ mandatoryResponseParameters.add("charges");
+ final Collection currentCharges = this.savingsAccountChargeReadPlatformService
+ .retrieveSavingsAccountCharges(accountId);
+ if (!CollectionUtils.isEmpty(currentCharges)) {
+ charges = currentCharges;
+ }
+ }
}
SavingsAccountData templateData = null;
@@ -168,7 +182,7 @@ public class SavingsAccountsApiResource {
savingsAccount.productId(), staffInSelectedOfficeOnly);
}
- return SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions);
+ return SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges);
}
@PUT
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java
index ae3ed42a7..191aa1623 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java
@@ -41,6 +41,8 @@ import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSeria
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService;
+import org.mifosplatform.portfolio.charge.data.ChargeData;
+import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType;
@@ -54,6 +56,7 @@ import org.mifosplatform.portfolio.savings.service.SavingsProductReadPlatformSer
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
@Path("/savingsproducts")
@Component
@@ -70,6 +73,7 @@ public class SavingsProductsApiResource {
private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService;
+ private final ChargeReadPlatformService chargeReadPlatformService;
@Autowired
public SavingsProductsApiResource(final SavingsProductReadPlatformService savingProductReadPlatformService,
@@ -79,7 +83,8 @@ public class SavingsProductsApiResource {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final ApiRequestParameterHelper apiRequestParameterHelper, final CodeValueReadPlatformService codeValueReadPlatformService,
final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService,
- final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService) {
+ final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService,
+ final ChargeReadPlatformService chargeReadPlatformService) {
this.savingProductReadPlatformService = savingProductReadPlatformService;
this.dropdownReadPlatformService = dropdownReadPlatformService;
this.currencyReadPlatformService = currencyReadPlatformService;
@@ -90,6 +95,7 @@ public class SavingsProductsApiResource {
this.codeValueReadPlatformService = codeValueReadPlatformService;
this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
this.accountMappingReadPlatformService = accountMappingReadPlatformService;
+ this.chargeReadPlatformService = chargeReadPlatformService;
}
@POST
@@ -141,6 +147,10 @@ public class SavingsProductsApiResource {
context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME);
SavingsProductData savingProductData = this.savingProductReadPlatformService.retrieveOne(productId);
+
+ final Collection charges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId);
+
+ savingProductData = SavingsProductData.withCharges(savingProductData, charges);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -220,19 +230,27 @@ public class SavingsProductsApiResource {
.retrieveAccountingRuleTypeOptions();
final Map> accountingMappingOptions = accountingDropdownReadPlatformService
.retrieveAccountMappingOptionsForSavingsProducts();
-
+
+ //charges
+ final boolean feeChargesOnly = true;
+ Collection chargeOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicableCharges(feeChargesOnly);
+ chargeOptions = CollectionUtils.isEmpty(chargeOptions) ? null : chargeOptions;
+
+ Collection penaltyOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicablePenalties();
+ penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
+
SavingsProductData savingsProductToReturn = null;
if (savingsProduct != null) {
savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
- accountingMappingOptions);
+ accountingMappingOptions, chargeOptions, penaltyOptions);
} else {
savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
- paymentTypeOptions, accountingRuleOptions, accountingMappingOptions);
+ paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions);
}
return savingsProductToReturn;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java
new file mode 100644
index 000000000..1b35ce6f4
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java
@@ -0,0 +1,127 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.savings.data;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import org.joda.time.LocalDate;
+import org.mifosplatform.infrastructure.core.data.EnumOptionData;
+import org.mifosplatform.organisation.monetary.data.CurrencyData;
+import org.mifosplatform.portfolio.charge.data.ChargeData;
+
+/**
+ * Immutable data object for Savings Account charge data.
+ */
+public class SavingsAccountChargeData {
+
+ @SuppressWarnings("unused")
+ private final Long id;
+
+ @SuppressWarnings("unused")
+ private final Long chargeId;
+
+ @SuppressWarnings("unused")
+ private final String name;
+
+ @SuppressWarnings("unused")
+ private final EnumOptionData chargeTimeType;
+
+ @SuppressWarnings("unused")
+ private final LocalDate dueDate;
+
+ private final EnumOptionData chargeCalculationType;
+
+ private final BigDecimal percentage;
+
+ @SuppressWarnings("unused")
+ private final BigDecimal amountPercentageAppliedTo;
+
+ @SuppressWarnings("unused")
+ private final CurrencyData currency;
+
+ private final BigDecimal amount;
+
+ @SuppressWarnings("unused")
+ private final BigDecimal amountPaid;
+
+ @SuppressWarnings("unused")
+ private final BigDecimal amountWaived;
+
+ @SuppressWarnings("unused")
+ private final BigDecimal amountWrittenOff;
+
+ @SuppressWarnings("unused")
+ private final BigDecimal amountOutstanding;
+
+ @SuppressWarnings("unused")
+ private final BigDecimal amountOrPercentage;
+
+ @SuppressWarnings("unused")
+ private final Collection chargeOptions;
+
+ @SuppressWarnings("unused")
+ private final boolean penalty;
+
+ public static SavingsAccountChargeData template(final Collection chargeOptions) {
+ final Long id = null;
+ final Long chargeId = null;
+ final String name = null;
+ final CurrencyData currency = null;
+ final BigDecimal amount = BigDecimal.ZERO;
+ final BigDecimal amountPaid = BigDecimal.ZERO;
+ final BigDecimal amountWaived = BigDecimal.ZERO;
+ final BigDecimal amountWrittenOff = BigDecimal.ZERO;
+ final BigDecimal amountOutstanding = BigDecimal.ZERO;
+ final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
+ final EnumOptionData chargeTimeType = null;
+ final EnumOptionData chargeCalculationType = null;
+ final BigDecimal percentage = BigDecimal.ZERO;
+ final boolean penalty = false;
+ final LocalDate dueAsOfDate = null;
+
+ return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, penalty);
+ }
+
+ public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final String name, final CurrencyData currency,
+ final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
+ final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate,
+ final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
+ final Collection chargeOptions, final boolean penalty) {
+
+ return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
+ amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff,
+ amountOutstanding, chargeOptions, penalty);
+ }
+
+ private SavingsAccountChargeData(Long id, Long chargeId, String name, EnumOptionData chargeTimeType, LocalDate dueAsOfDate,
+ EnumOptionData chargeCalculationType, BigDecimal percentage, BigDecimal amountPercentageAppliedTo, CurrencyData currency,
+ BigDecimal amount, BigDecimal amountPaid, BigDecimal amountWaived, BigDecimal amountWrittenOff, BigDecimal amountOutstanding,
+ Collection chargeOptions, boolean penalty) {
+ this.id = id;
+ this.chargeId = chargeId;
+ this.name = name;
+ this.chargeTimeType = chargeTimeType;
+ this.dueDate = dueAsOfDate;
+ this.chargeCalculationType = chargeCalculationType;
+ this.percentage = percentage;
+ this.amountPercentageAppliedTo = amountPercentageAppliedTo;
+ this.currency = currency;
+ this.amount = amount;
+ this.amountPaid = amountPaid;
+ this.amountWaived = amountWaived;
+ this.amountWrittenOff = amountWrittenOff;
+ this.amountOutstanding = amountOutstanding;
+ this.amountOrPercentage = this.getAmountOrPercentage();
+ this.chargeOptions = chargeOptions;
+ this.penalty = penalty;
+ }
+
+ private BigDecimal getAmountOrPercentage() {
+ return (this.chargeCalculationType != null) && (chargeCalculationType.getId().intValue() > 1) ? this.percentage : this.amount;
+ }
+
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java
new file mode 100644
index 000000000..a2bb1c388
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java
@@ -0,0 +1,89 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.mifosplatform.portfolio.savings.data;
+
+import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS;
+import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME;
+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 java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.mifosplatform.infrastructure.core.data.ApiParameterError;
+import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
+import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
+import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class SavingsAccountChargeDataValidator {
+
+ private final FromJsonHelper fromApiJsonHelper;
+
+ @Autowired
+ public SavingsAccountChargeDataValidator(final FromJsonHelper fromApiJsonHelper) {
+ this.fromApiJsonHelper = fromApiJsonHelper;
+ }
+
+ public void validateAdd(final String json) {
+ if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+ final Type typeOfMap = new TypeToken