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: +

+

+

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>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME); + + final JsonElement element = fromApiJsonHelper.parse(json); + final Long chargeId = fromApiJsonHelper.extractLongNamed(chargeIdParamName, element); + baseDataValidator.reset().parameter(chargeIdParamName).value(chargeId).notNull().integerGreaterThanZero(); + + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element); + baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); + + if (fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) { + fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateUpdate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME); + + final JsonElement element = fromApiJsonHelper.parse(json); + + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element); + baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); + + if (fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) { + fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java index e50eadff5..e08d966b3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java @@ -15,6 +15,7 @@ import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.staff.data.StaffData; +import org.mifosplatform.portfolio.charge.data.ChargeData; /** * Immutable data object representing a savings account. @@ -54,6 +55,7 @@ public class SavingsAccountData { private final SavingsAccountSummaryData summary; @SuppressWarnings("unused") private final Collection transactions; + private final Collection charges; // template private final Collection productOptions; @@ -64,6 +66,7 @@ public class SavingsAccountData { private final Collection interestCalculationDaysInYearTypeOptions; private final Collection lockinPeriodFrequencyTypeOptions; private final Collection withdrawalFeeTypeOptions; + private final Collection chargeOptions; public static SavingsAccountData instance(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId, final String productName, @@ -86,6 +89,8 @@ public class SavingsAccountData { final Collection withdrawalFeeTypeOptions = null; final Collection transactions = null; + final Collection charges = null; + final Collection chargeOptions = null; return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType, @@ -93,7 +98,7 @@ public class SavingsAccountData { lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions); } public static SavingsAccountData lookup(final Long accountId, final String accountNo) { @@ -108,16 +113,18 @@ public class SavingsAccountData { final Collection withdrawalFeeTypeOptions = null; final Collection transactions = null; + final Collection charges = null; + final Collection chargeOptions = null; return new SavingsAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, false, null, null, null, null, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions); + withdrawalFeeTypeOptions, charges, chargeOptions); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template, - final Collection transactions) { + final Collection transactions, final Collection charges) { if (template == null) { final Collection productOptions = null; @@ -128,10 +135,11 @@ public class SavingsAccountData { final Collection interestCalculationDaysInYearTypeOptions = null; final Collection lockinPeriodFrequencyTypeOptions = null; final Collection withdrawalFeeTypeOptions = null; + final Collection chargeOptions = null; return withTemplateOptions(account, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); + lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions); } return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, @@ -143,7 +151,7 @@ public class SavingsAccountData { account.annualFeeAmount, account.annualFeeOnMonthDay, account.annualFeeNextDueDate, account.summary, transactions, template.productOptions, template.fieldOfficerOptions, template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions, template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions, - template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions); + template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, @@ -153,7 +161,7 @@ public class SavingsAccountData { final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, - final Collection transactions) { + final Collection transactions, final Collection charges, final Collection chargeOptions) { return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.fieldOfficerId, @@ -164,7 +172,7 @@ public class SavingsAccountData { account.annualFeeAmount, account.annualFeeOnMonthDay, account.annualFeeNextDueDate, account.summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions); + withdrawalFeeTypeOptions, charges, chargeOptions); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -207,13 +215,16 @@ public class SavingsAccountData { final Collection lockinPeriodFrequencyTypeOptions = null; final Collection withdrawalFeeTypeOptions = null; + final Collection charges = null; + final Collection chargeOptions = null; + return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,withdrawalFeeForTransfers, annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions); } private SavingsAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName, @@ -230,7 +241,7 @@ public class SavingsAccountData { final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, - final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions) { + final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection charges, final Collection chargeOptions) { this.id = id; this.accountNo = accountNo; this.externalId = externalId; @@ -271,6 +282,9 @@ public class SavingsAccountData { this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions; this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions; this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions; + + this.charges = charges;//charges associated with Savings account + this.chargeOptions = chargeOptions;//charges available for adding to Savings account } public Long id() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountSummaryData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountSummaryData.java index 418bb375b..d2730da62 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountSummaryData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountSummaryData.java @@ -10,7 +10,7 @@ import java.math.BigDecimal; import org.mifosplatform.organisation.monetary.data.CurrencyData; /** - * Immutable data object representing loan summary information. + * Immutable data object representing Savings Account summary information. */ @SuppressWarnings("unused") public class SavingsAccountSummaryData { @@ -23,10 +23,13 @@ public class SavingsAccountSummaryData { private final BigDecimal totalInterestEarned; private final BigDecimal totalInterestPosted; private final BigDecimal accountBalance; + private final BigDecimal totalFeeCharge; + private final BigDecimal totalPenaltyCharge; public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals, final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned, - final BigDecimal totalInterestPosted, final BigDecimal accountBalance) { + final BigDecimal totalInterestPosted, final BigDecimal accountBalance, final BigDecimal totalFeeCharge, + final BigDecimal totalPenaltyCharge) { this.currency = currency; this.totalDeposits = totalDeposits; this.totalWithdrawals = totalWithdrawals; @@ -35,5 +38,7 @@ public class SavingsAccountSummaryData { this.totalInterestEarned = totalInterestEarned; this.totalInterestPosted = totalInterestPosted; this.accountBalance = accountBalance; + this.totalFeeCharge = totalFeeCharge; + this.totalPenaltyCharge = totalPenaltyCharge; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java index 721554a37..d54596758 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java @@ -17,6 +17,7 @@ import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGL import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.charge.data.ChargeData; /** * Immutable data object represent a savings product. @@ -46,28 +47,23 @@ public class SavingsProductData { private final Map accountingMappings; private final Collection paymentChannelToFundSourceMappings; + // charges + private final Collection charges; + // template - @SuppressWarnings("unused") private final Collection currencyOptions; - @SuppressWarnings("unused") private final Collection interestCompoundingPeriodTypeOptions; - @SuppressWarnings("unused") private final Collection interestPostingPeriodTypeOptions; - @SuppressWarnings("unused") private final Collection interestCalculationTypeOptions; - @SuppressWarnings("unused") private final Collection interestCalculationDaysInYearTypeOptions; - @SuppressWarnings("unused") private final Collection lockinPeriodFrequencyTypeOptions; - @SuppressWarnings("unused") private final Collection withdrawalFeeTypeOptions; - @SuppressWarnings("unused") private final Collection paymentTypeOptions; - @SuppressWarnings("unused") private final Collection accountingRuleOptions; - @SuppressWarnings("unused") private final Map> accountingMappingOptions; - + private final Collection chargeOptions; + private final Collection penaltyOptions; + public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule, @@ -77,7 +73,7 @@ public class SavingsProductData { final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, - final Map> accountingMappingOptions) { + final Map> accountingMappingOptions, final Collection chargeOptions, final Collection penaltyOptions) { final Long id = null; final String name = null; @@ -93,6 +89,7 @@ public class SavingsProductData { final MonthDay annualFeeMonthDay = null; final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; + final Collection charges = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -100,9 +97,22 @@ public class SavingsProductData { accountingRule, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); } + public static SavingsProductData withCharges(final SavingsProductData product, final Collection charges) { + return new SavingsProductData(product.id, product.name, product.description, product.currency, product.nominalAnnualInterestRate, + product.interestCompoundingPeriodType, product.interestPostingPeriodType, product.interestCalculationType, + product.interestCalculationDaysInYearType, product.minRequiredOpeningBalance, product.lockinPeriodFrequency, + product.lockinPeriodFrequencyType, product.withdrawalFeeAmount, product.withdrawalFeeType, + product.withdrawalFeeForTransfers, product.accountingRule, product.annualFeeAmount, product.annualFeeOnMonthDay, + product.accountingMappings, product.paymentChannelToFundSourceMappings, product.currencyOptions, + product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions, + product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions, + product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions, + product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions); + } + /** * Returns a {@link SavingsProductData} that contains and exist * {@link SavingsProductData} data with further template data for dropdowns. @@ -114,7 +124,7 @@ public class SavingsProductData { final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, - final Map> accountingMappingOptions) { + final Map> accountingMappingOptions, final Collection chargeOptions, final Collection penaltyOptions) { return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, @@ -126,7 +136,7 @@ public class SavingsProductData { existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, - accountingMappingOptions); + accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions); } public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct, @@ -142,6 +152,8 @@ public class SavingsProductData { final Collection paymentTypeOptions = null; final Collection accountingRuleOptions = null; final Map> accountingMappingOptions = null; + final Collection chargeOptions = null; + final Collection penaltyOptions = null; return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, @@ -152,7 +164,7 @@ public class SavingsProductData { existingProduct.annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions); + accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions); } public static SavingsProductData instance(final Long id, final String name, final String description, final CurrencyData currency, @@ -176,6 +188,9 @@ public class SavingsProductData { final Collection paymentTypeOptions = null; final Collection accountingRuleOptions = null; final Map> accountingMappingOptions = null; + final Collection chargeOptions = null; + final Collection penaltyOptions = null; + final Collection charges = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -183,7 +198,7 @@ public class SavingsProductData { annualFeeAmount, annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); } public static SavingsProductData lookup(final Long id, final String name) { @@ -217,6 +232,9 @@ public class SavingsProductData { final Collection paymentTypeOptions = null; final Collection accountingRuleOptions = null; final Map> accountingMappingOptions = null; + final Collection charges = null; + final Collection chargeOptions = null; + final Collection penaltyOptions = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -224,7 +242,7 @@ public class SavingsProductData { annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); } private SavingsProductData(final Long id, final String name, final String description, final CurrencyData currency, @@ -241,7 +259,7 @@ public class SavingsProductData { final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, - final Map> accountingMappingOptions) { + final Map> accountingMappingOptions, final Collection charges, final Collection chargeOptions, final Collection penaltyOptions) { this.id = id; this.name = name; this.description = description; @@ -278,6 +296,10 @@ public class SavingsProductData { this.accountingMappings = accountingMappings; } this.paymentChannelToFundSourceMappings = paymentChannelToFundSourceMappings; + + this.charges = charges;//charges associated with Savings product + this.chargeOptions = chargeOptions;//charges available for adding to Savings product + this.penaltyOptions = penaltyOptions;//penalties available for adding to Savings product } public boolean hasAccountingEnabled() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 325834aad..d91fed89c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -22,10 +22,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -62,6 +64,8 @@ import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.portfolio.accountdetails.domain.AccountType; +import org.mifosplatform.portfolio.charge.domain.Charge; +import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; @@ -78,6 +82,9 @@ import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceE import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; +import org.springframework.util.CollectionUtils; + +import com.google.gson.JsonArray; @Entity @Table(name = "m_savings_account", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "sa_account_no_UNIQUE"), @@ -242,6 +249,10 @@ public class SavingsAccount extends AbstractPersistable { @LazyCollection(LazyCollectionOption.FALSE) @OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true) private final List transactions = new ArrayList(); + + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true) + private Set charges = new HashSet(); @Transient private boolean accountNumberRequiresAutoGeneration = false; @@ -262,13 +273,15 @@ public class SavingsAccount extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, - final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay) { + final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, + final Set savingsAccountCharges) { final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeApplicableForTransfer, annualFeeAmount, annualFeeOnMonthDay); + withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeApplicableForTransfer, annualFeeAmount, annualFeeOnMonthDay, + savingsAccountCharges); } private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, @@ -279,7 +292,8 @@ public class SavingsAccount extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, - final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay) { + final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, + final Set savingsAccountCharges) { this.client = client; this.group = group; this.product = product; @@ -317,6 +331,12 @@ public class SavingsAccount extends AbstractPersistable { this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth(); } + if (!CollectionUtils.isEmpty(savingsAccountCharges)) { + this.charges = associateChargesWithThisSavingsAccount(savingsAccountCharges); + //TODO:Add charges details to savings summary + //this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); + } + this.summary = new SavingsAccountSummary(); } @@ -1074,12 +1094,23 @@ public class SavingsAccount extends AbstractPersistable { this.annualFeeOnMonth = monthOfYear; } } + + //charges + final String chargesParamName = "charges"; + if (command.hasParameter(chargesParamName)) { + JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName); + if (jsonArray != null) { + actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName)); + } + } // set period type to null if frequency is null if (this.annualFeeAmount == null) { this.annualFeeOnDay = null; this.annualFeeOnMonth = null; } + + validateLockinDetails(); validateWithdrawalFeeDetails(); @@ -1457,6 +1488,13 @@ public class SavingsAccount extends AbstractPersistable { .withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay).plusYears(1); this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate(); } + } else if (transactionToUndo.isCharge()) { + //undo charge + final Set chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid(); + for (SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) { + final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge(); + chargeToUndo.resetPaidAmount(currency); + } } } @@ -1809,5 +1847,193 @@ public class SavingsAccount extends AbstractPersistable { public void setStatus(final Integer status) { this.status = status; } + + private Set associateChargesWithThisSavingsAccount(final Set savingsAccountCharges) { + for (final SavingsAccountCharge savingsAccountCharge : savingsAccountCharges) { + savingsAccountCharge.update(this); + } + return savingsAccountCharges; + } + + public boolean update(final Set newSavingsAccountCharges) { + if (newSavingsAccountCharges == null) return false; + if(this.charges == null) this.charges = new HashSet(); + this.charges.clear(); + this.charges.addAll(associateChargesWithThisSavingsAccount(newSavingsAccountCharges)); + return true; + } + + public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) { + //TODO AA: revisit + if(this.currency == null) return false; + return this.currency.getCode().equalsIgnoreCase(matchingCurrencyCode); + } + + public void removeCharge(final SavingsAccountCharge charge){ + this.charges.remove(charge); + } + + public void waiveCharge(final Long savingsAccountChargeId, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + + 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); } + } + + final SavingsAccountCharge savingsAccountCharge = this.getCharge(savingsAccountChargeId); + + // validate charge is not already paid or waived + if(savingsAccountCharge.isWaived()){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + }else if(savingsAccountCharge.isPaid()){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + + //waive charge + final Money amountWaived = savingsAccountCharge.waive(this.getCurrency()); + //TODO : AA revisit the code + /*Money feeChargesWaived = amountWaived; + Money penaltyChargesWaived = Money.zero(this.getCurrency()); + if (savingsAccountCharge.isPenaltyCharge()) { + penaltyChargesWaived = amountWaived; + feeChargesWaived = Money.zero(this.getCurrency()); + }*/ + + //persist an entry in transaction details + SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, this.office(), savingsAccountCharge.getDueLocalDate(), amountWaived); + + this.transactions.add(chargeTransaction); + + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + final MathContext mc = MathContext.DECIMAL64; + calculateInterestUsing(mc, savingsAccountCharge.getDueLocalDate()); + } + + public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition){ + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + + 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); } + } + + if (!this.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.currency.and.charge.currency.not.same"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate(); + + if (chargeDueDate != null && chargeDueDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset().parameter("dueDate").value(getActivationLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + //add new charge to savings account + getCharges().add(savingsAccountCharge); + + } + + public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + + 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); } + } + + final SavingsAccountCharge savingsAccountCharge = this.getCharge(savingsAccountChargeId); + + // validate charge is not already paid or waived + if(savingsAccountCharge.isWaived()){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + }else if(savingsAccountCharge.isPaid()){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + + final Money chargePaid = savingsAccountCharge.pay(this.getCurrency(), amountPaid); + + final LocalDate transactionDate = (savingsAccountCharge.getDueLocalDate() == null) ? DateUtils.getLocalDateOfTenant() + : savingsAccountCharge.getDueLocalDate();//If null reset to current date + //persist an entry in transaction details + SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, this.office(), transactionDate, chargePaid); + + final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, amountPaid); + chargeTransaction.getSavingsAccountChargesPaid().add(chargePaidBy); + this.transactions.add(chargeTransaction); + + final MathContext mc = MathContext.DECIMAL64; + if (isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + calculateInterestUsing(mc, today); + } + + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + + validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.payChargeTransactionAction); + + } + + + private SavingsAccountCharge getCharge(Long savingsAccountChargeId) { + SavingsAccountCharge charge = null; + for (SavingsAccountCharge existingCharge : this.charges) { + if(existingCharge.getId().equals(savingsAccountChargeId)) { + charge = existingCharge; + break; + } + } + + if(charge == null){ + throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId, getId()); + } + + return charge; + } + + private Set getCharges(){ + return (this.charges == null) ? new HashSet() : this.charges; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index 6571dfbbf..b10dc9e0d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -27,6 +27,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawal import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import java.math.BigDecimal; +import java.util.Set; import org.joda.time.LocalDate; import org.joda.time.MonthDay; @@ -66,19 +67,22 @@ public class SavingsAccountAssembler { private final StaffRepositoryWrapper staffRepository; private final SavingsProductRepository savingProductRepository; private final SavingsAccountRepositoryWrapper savingsAccountRepository; + private final SavingsAccountChargeAssembler savingsAccountChargeAssembler; private final FromJsonHelper fromApiJsonHelper; @Autowired public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository, final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository, - final SavingsAccountRepositoryWrapper savingsAccountRepository, final FromJsonHelper fromApiJsonHelper) { + final SavingsAccountRepositoryWrapper savingsAccountRepository, + final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper) { this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.clientRepository = clientRepository; this.groupRepository = groupRepository; this.staffRepository = staffRepository; this.savingProductRepository = savingProductRepository; this.savingsAccountRepository = savingsAccountRepository; + this.savingsAccountChargeAssembler = savingsAccountChargeAssembler; this.fromApiJsonHelper = fromApiJsonHelper; } @@ -231,12 +235,14 @@ public class SavingsAccountAssembler { } else { monthDayOfAnnualFee = product.monthDayOfAnnualFee(); } + + final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(element); final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, externalId, accountType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, iswithdrawalFeeApplicableForTransfer, annualFeeAmount, - monthDayOfAnnualFee); + monthDayOfAnnualFee, charges); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java new file mode 100644 index 000000000..21f0c92a1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java @@ -0,0 +1,503 @@ +/** + * 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.domain; + +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dateFormatParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.charge.domain.Charge; +import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; +import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; +import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeWithoutMandatoryFieldException; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_savings_account_charge") +public class SavingsAccountCharge extends AbstractPersistable { + + @ManyToOne(optional = false) + @JoinColumn(name = "savings_account_id", referencedColumnName = "id", nullable = false) + private SavingsAccount savingsAccount; + + @ManyToOne(optional = false) + @JoinColumn(name = "charge_id", referencedColumnName = "id", nullable = false) + private Charge charge; + + @Column(name = "charge_time_enum", nullable = false) + private Integer chargeTime; + + @Temporal(TemporalType.DATE) + @Column(name = "due_for_collection_as_of_date") + private Date dueDate; + + @Column(name = "charge_calculation_enum") + private Integer chargeCalculation; + + @Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true) + private BigDecimal percentage; + + @Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true) + private BigDecimal amountPercentageAppliedTo; + + @Column(name = "amount", scale = 6, precision = 19, nullable = false) + private BigDecimal amount; + + @Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true) + private BigDecimal amountPaid; + + @Column(name = "amount_waived_derived", scale = 6, precision = 19, nullable = true) + private BigDecimal amountWaived; + + @Column(name = "amount_writtenoff_derived", scale = 6, precision = 19, nullable = true) + private BigDecimal amountWrittenOff; + + @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false) + private BigDecimal amountOutstanding; + + @Column(name = "is_penalty", nullable = false) + private boolean penaltyCharge = false; + + @Column(name = "is_paid_derived", nullable = false) + private boolean paid = false; + + @Column(name = "waived", nullable = false) + private boolean waived = false; + + public static SavingsAccountCharge createNewFromJson(final SavingsAccount savingsAccount, final Charge chargeDefinition, final JsonCommand command) { + + final BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName); + final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); + final ChargeTimeType chargeTime = null; + final ChargeCalculationType chargeCalculation = null; + + return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate); + } + + //TODO AA: refactor method signature + public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amount, + final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) { + return new SavingsAccountCharge(null, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate); + } + + protected SavingsAccountCharge() { + // + } + + public SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount, + final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) { + + this.savingsAccount = savingsAccount; + this.charge = chargeDefinition; + this.penaltyCharge = chargeDefinition.isPenalty(); + this.chargeTime = (chargeTime == null) ? chargeDefinition.getChargeTime() : chargeTime.getValue(); + + if (isOnSpecifiedDueDate()) { + if (dueDate == null) { + final String defaultUserMessage = "Savings Account charge is missing due date."; + throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName, defaultUserMessage, chargeDefinition.getId(), + chargeDefinition.getName()); + } + + } + + this.dueDate = (dueDate == null) ? null : dueDate.toDate(); + + this.chargeCalculation = chargeDefinition.getChargeCalculation(); + if (chargeCalculation != null) { + this.chargeCalculation = chargeCalculation.getValue(); + } + + BigDecimal chargeAmount = chargeDefinition.getAmount(); + if (amount != null) { + chargeAmount = amount; + } + + final BigDecimal transactionAmount = new BigDecimal(0);//percentage based fee is not yet supported for savings + populateDerivedFields(transactionAmount, chargeAmount); + this.paid = determineIfFullyPaid(); + } + + private void populateDerivedFields(final BigDecimal transactionAmount, final BigDecimal chargeAmount) { + + switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { + case INVALID: + this.percentage = null; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountPaid = null; + this.amountOutstanding = BigDecimal.ZERO; + this.amountWaived = null; + this.amountWrittenOff = null; + break; + case FLAT: + this.percentage = null; + this.amount = chargeAmount; + this.amountPercentageAppliedTo = null; + this.amountPaid = null; + this.amountOutstanding = chargeAmount; + this.amountWaived = null; + this.amountWrittenOff = null; + break; + case PERCENT_OF_AMOUNT: + this.percentage = chargeAmount; + this.amountPercentageAppliedTo = transactionAmount; + this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage); + this.amountPaid = null; + this.amountOutstanding = calculateOutstanding(); + this.amountWaived = null; + this.amountWrittenOff = null; + break; + case PERCENT_OF_AMOUNT_AND_INTEREST: + this.percentage = null; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountPaid = null; + this.amountOutstanding = BigDecimal.ZERO; + this.amountWaived = null; + this.amountWrittenOff = null; + break; + case PERCENT_OF_INTEREST: + this.percentage = null; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountPaid = null; + this.amountOutstanding = BigDecimal.ZERO; + this.amountWaived = null; + this.amountWrittenOff = null; + break; + } + } + + public void markAsFullyPaid() { + this.amountPaid = this.amount; + this.amountOutstanding = BigDecimal.ZERO; + this.paid = true; + } + + public void resetToOriginal(final MonetaryCurrency currency) { + this.amountPaid = BigDecimal.ZERO; + this.amountWaived = BigDecimal.ZERO; + this.amountWrittenOff = BigDecimal.ZERO; + this.amountOutstanding = calculateAmountOutstanding(currency); + this.paid = false; + this.waived = false; + } + + public void resetPaidAmount(final MonetaryCurrency currency) { + this.amountPaid = BigDecimal.ZERO; + this.amountOutstanding = calculateAmountOutstanding(currency); + this.paid = false; + } + + public Money waive(final MonetaryCurrency currency) { + this.amountWaived = this.amountOutstanding; + this.amountOutstanding = BigDecimal.ZERO; + this.paid = false; + this.waived = true; + return getAmountWaived(currency); + } + + public Money pay(final MonetaryCurrency currency, final BigDecimal chargePaid) { + + Money amountPaidToDate = Money.of(currency, this.amountPaid); + Money amountPaid = Money.of(currency, chargePaid); + Money amountOutstanding = Money.of(currency, this.amountOutstanding); + amountPaidToDate = amountPaidToDate.plus(amountPaid); + amountOutstanding = amountOutstanding.minus(chargePaid); + this.amountPaid = amountPaidToDate.getAmount(); + this.amountOutstanding = amountOutstanding.getAmount(); + this.paid = determineIfFullyPaid(); + return amountPaid; + } + + private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) { + return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount(); + } + + public void update(final SavingsAccount savingsAccount) { + this.savingsAccount = savingsAccount; + } + + public void update(final BigDecimal amount, final LocalDate dueDate) { + final BigDecimal transactionAmount = BigDecimal.ZERO;//percentage based fee calculation is not yet supported for Savings + if (dueDate != null) { + this.dueDate = dueDate.toDate(); + } + + if (amount != null) { + switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { + case INVALID: + break; + case FLAT: + this.amount = amount; + break; + case PERCENT_OF_AMOUNT: + this.percentage = amount; + this.amountPercentageAppliedTo = transactionAmount; + this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage); + this.amountOutstanding = calculateOutstanding(); + break; + case PERCENT_OF_AMOUNT_AND_INTEREST: + this.percentage = amount; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountOutstanding = null; + break; + case PERCENT_OF_INTEREST: + this.percentage = amount; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountOutstanding = null; + break; + } + } + } + + public Map update(final JsonCommand command) { + + final Map actualChanges = new LinkedHashMap(7); + + final String dateFormatAsInput = command.dateFormat(); + final String localeAsInput = command.locale(); + + + if (command.isChangeInLocalDateParameterNamed(dueAsOfDateParamName, getDueLocalDate())) { + final String valueAsInput = command.stringValueOfParameterNamed(dueAsOfDateParamName); + actualChanges.put(dueAsOfDateParamName, valueAsInput); + actualChanges.put(dateFormatParamName, dateFormatAsInput); + actualChanges.put(localeParamName, localeAsInput); + + final LocalDate newValue = command.localDateValueOfParameterNamed(dueAsOfDateParamName); + this.dueDate = newValue.toDate(); + } + + if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName); + actualChanges.put(amountParamName, newValue); + actualChanges.put(localeParamName, localeAsInput); + switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { + case INVALID: + break; + case FLAT: + this.amount = newValue; + break; + case PERCENT_OF_AMOUNT: + this.percentage = newValue; + this.amountPercentageAppliedTo = null; + this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage); + this.amountOutstanding = calculateOutstanding(); + break; + case PERCENT_OF_AMOUNT_AND_INTEREST: + this.percentage = newValue; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountOutstanding = null; + break; + case PERCENT_OF_INTEREST: + this.percentage = newValue; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountOutstanding = null; + break; + } + } + + return actualChanges; + } + + public boolean isSpecifiedDueDate() { + return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE); + } + + private boolean isGreaterThanZero(final BigDecimal value) { + return value.compareTo(BigDecimal.ZERO) == 1; + } + + public LocalDate getDueLocalDate() { + LocalDate dueDate = null; + if (this.dueDate != null) { + dueDate = new LocalDate(this.dueDate); + } + return dueDate; + } + + private boolean determineIfFullyPaid() { + return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0; + } + + private BigDecimal calculateOutstanding() { + + BigDecimal amountPaidLocal = BigDecimal.ZERO; + if (this.amountPaid != null) { + amountPaidLocal = this.amountPaid; + } + + BigDecimal amountWaivedLocal = BigDecimal.ZERO; + if (this.amountWaived != null) { + amountWaivedLocal = this.amountWaived; + } + + BigDecimal amountWrittenOffLocal = BigDecimal.ZERO; + if (this.amountWrittenOff != null) { + amountWrittenOffLocal = this.amountWrittenOff; + } + + final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal); + + return this.amount.subtract(totalAccountedFor); + } + + private BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage) { + + BigDecimal percentageOf = BigDecimal.ZERO; + + if (isGreaterThanZero(value)) { + final MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); + BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100l), mc); + percentageOf = value.multiply(multiplicand, mc); + } + + return percentageOf; + } + + public BigDecimal amount() { + return this.amount; + } + + public boolean isFeeCharge() { + return !this.penaltyCharge; + } + + public boolean isPenaltyCharge() { + return this.penaltyCharge; + } + + public boolean isNotFullyPaid() { + return !isPaid(); + } + + public boolean isPaid() { + return this.paid; + } + + public boolean isWaived() { + return this.waived; + } + + public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) { + + final Money amountWaivedOrWrittenOff = getAmountWaived(currency).plus(getAmountWrittenOff(currency)); + return Money.of(currency, this.amountPaid).plus(amountWaivedOrWrittenOff).isGreaterThanZero(); + } + + public Money getAmount(final MonetaryCurrency currency) { + return Money.of(currency, this.amount); + } + + private Money getAmountPaid(final MonetaryCurrency currency) { + return Money.of(currency, this.amountPaid); + } + + public Money getAmountWaived(final MonetaryCurrency currency) { + return Money.of(currency, this.amountWaived); + } + + public Money getAmountWrittenOff(final MonetaryCurrency currency) { + return Money.of(currency, this.amountWrittenOff); + } + + /** + * @param incrementBy + * Amount used to pay off this charge + * @return Actual amount paid on this charge + */ + public Money updatePaidAmountBy(final Money incrementBy) { + + Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid); + Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding); + + Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency()); + if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) { + amountPaidOnThisCharge = amountOutstanding; + amountPaidToDate = amountPaidToDate.plus(amountOutstanding); + this.amountPaid = amountPaidToDate.getAmount(); + this.amountOutstanding = BigDecimal.ZERO; + } else { + amountPaidOnThisCharge = incrementBy; + amountPaidToDate = amountPaidToDate.plus(incrementBy); + this.amountPaid = amountPaidToDate.getAmount(); + + Money amountExpected = Money.of(incrementBy.getCurrency(), this.amount); + this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount(); + } + + this.paid = determineIfFullyPaid(); + + return amountPaidOnThisCharge; + } + + public String name() { + return this.charge.getName(); + } + + public String currencyCode() { + return this.charge.getCurrencyCode(); + } + + public Charge getCharge() { + return this.charge; + } + + public boolean isOnSpecifiedDueDate(){ + return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { return false; } + if (obj == this) { return true; } + if (obj.getClass() != getClass()) { return false; } + SavingsAccountCharge rhs = (SavingsAccountCharge) obj; + return new EqualsBuilder().appendSuper(super.equals(obj)) // + .append(this.getId(), rhs.getId()) // + .append(this.charge.getId(), rhs.charge.getId()) // + .append(this.amount, rhs.amount) // + .append(this.getDueLocalDate(), rhs.getDueLocalDate()) // + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(3, 5) // + .append(this.getId()) // + .append(this.charge.getId()) // + .append(this.amount).append(this.getDueLocalDate()) // + .toHashCode(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java new file mode 100644 index 000000000..ea72111d1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java @@ -0,0 +1,107 @@ +/** + * 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.domain; + +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeCalculationTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.charge.domain.Charge; +import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; +import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; +import org.mifosplatform.portfolio.charge.exception.ChargeCannotBeAppliedToException; +import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +@Service +public class SavingsAccountChargeAssembler { + + private final FromJsonHelper fromApiJsonHelper; + private final ChargeRepositoryWrapper chargeRepository; + private final SavingsAccountChargeRepository savingsAccountChargeRepository; + + @Autowired + public SavingsAccountChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository, + final SavingsAccountChargeRepository savingsAccountChargeRepository) { + this.fromApiJsonHelper = fromApiJsonHelper; + this.chargeRepository = chargeRepository; + this.savingsAccountChargeRepository = savingsAccountChargeRepository; + } + + public Set fromParsedJson(final JsonElement element) { + + final Set savingsAccountCharges = new HashSet(); + + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + + final JsonObject savingsChargeElement = array.get(i).getAsJsonObject(); + + final Long id = fromApiJsonHelper.extractLongNamed(idParamName, savingsChargeElement); + final Long chargeId = fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement); + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale); + final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed(chargeTimeTypeParamName, savingsChargeElement, locale); + final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed(chargeCalculationTypeParamName, savingsChargeElement, + locale); + final LocalDate dueDate = fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement, + dateFormat, locale); + + if (id == null) { + final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId); + + if(!chargeDefinition.isSavingsCharge()){ + String errorMessage = "Charge with identifier " + chargeDefinition.getId() + " cannot be applied to Savings product."; + throw new ChargeCannotBeAppliedToException("savings.product", errorMessage, chargeDefinition.getId()); + } + + ChargeTimeType chargeTime = null; + if (chargeTimeType != null) { + ChargeTimeType.fromInt(chargeTimeType); + } + ChargeCalculationType chargeCalculation = null; + if (chargeCalculationType != null) { + ChargeCalculationType.fromInt(chargeCalculationType); + } + final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(chargeDefinition, amount, chargeTime, chargeCalculation, dueDate); + savingsAccountCharges.add(savingsAccountCharge); + } else { + final Long savingsAccountChargeId = id; + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOne(savingsAccountChargeId); + if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); } + + savingsAccountCharge.update(amount, dueDate); + + savingsAccountCharges.add(savingsAccountCharge); + } + } + } + } + + return savingsAccountCharges; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargePaidBy.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargePaidBy.java new file mode 100644 index 000000000..25393cadf --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargePaidBy.java @@ -0,0 +1,73 @@ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_savings_account_charge_paid_by") +public class SavingsAccountChargePaidBy extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "savings_account_transaction_id", nullable = false) + private SavingsAccountTransaction savingsAccountTransaction; + + @ManyToOne + @JoinColumn(name = "savings_account_charge_id", nullable = false) + private SavingsAccountCharge savingsAccountCharge; + + @Column(name = "amount", scale = 6, precision = 19, nullable = false) + private BigDecimal amount; + + protected SavingsAccountChargePaidBy() { + + } + + public static SavingsAccountChargePaidBy instance(final SavingsAccountTransaction savingsAccountTransaction, final SavingsAccountCharge savingsAccountCharge, final BigDecimal amount){ + return new SavingsAccountChargePaidBy(savingsAccountTransaction, savingsAccountCharge, amount); + } + + private SavingsAccountChargePaidBy(SavingsAccountTransaction savingsAccountTransaction, SavingsAccountCharge savingsAccountCharge, BigDecimal amount) { + this.savingsAccountTransaction = savingsAccountTransaction; + this.savingsAccountCharge = savingsAccountCharge; + this.amount = amount; + } + + public SavingsAccountTransaction getSavingsAccountTransaction() { + return this.savingsAccountTransaction; + } + + public void setSavingsAccountTransaction(SavingsAccountTransaction savingsAccountTransaction) { + this.savingsAccountTransaction = savingsAccountTransaction; + } + + public SavingsAccountCharge getSavingsAccountCharge() { + return this.savingsAccountCharge; + } + + public void setSavingsAccountCharge(SavingsAccountCharge savingsAccountCharge) { + this.savingsAccountCharge = savingsAccountCharge; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public boolean isFeeCharge(){ + return (this.savingsAccountCharge == null) ? false : this.savingsAccountCharge.isFeeCharge(); + } + + public boolean isPenaltyCharge(){ + return (this.savingsAccountCharge == null) ? false : this.savingsAccountCharge.isPenaltyCharge(); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java new file mode 100644 index 000000000..236496e1c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java @@ -0,0 +1,13 @@ +/** + * 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.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface SavingsAccountChargeRepository extends JpaRepository, JpaSpecificationExecutor { + SavingsAccountCharge findByIdAndSavingsAccountId(Long id, Long savingsAccountId); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java new file mode 100644 index 000000000..41bc230d8 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java @@ -0,0 +1,51 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * Wrapper for {@link SavingsAccountChargeRepository} that adds NULL checking and Error + * handling capabilities + *

+ */ +@Service +public class SavingsAccountChargeRepositoryWrapper { + + private final SavingsAccountChargeRepository repository; + + @Autowired + public SavingsAccountChargeRepositoryWrapper(final SavingsAccountChargeRepository repository) { + this.repository = repository; + } + + public SavingsAccountCharge findOneWithNotFoundDetection(final Long id) { + final SavingsAccountCharge savingsAccountCharge = this.repository.findOne(id); + if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(id); } + return savingsAccountCharge; + } + + public SavingsAccountCharge findOneWithNotFoundDetection(final Long id, final Long savingsAccountId) { + final SavingsAccountCharge savingsAccountCharge = this.repository.findByIdAndSavingsAccountId(id, savingsAccountId); + if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(id); } + return savingsAccountCharge; + } + + public void save(final SavingsAccountCharge savingsAccountCharge) { + this.repository.save(savingsAccountCharge); + } + + public void saveAndFlush(final SavingsAccountCharge savingsAccountCharge) { + this.repository.saveAndFlush(savingsAccountCharge); + } + + public void delete(final SavingsAccountCharge savingsAccountCharge) { + this.repository.delete(savingsAccountCharge); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java index 846253b27..6c66e818e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java @@ -36,6 +36,12 @@ public final class SavingsAccountSummary { @Column(name = "total_withdrawal_fees_derived", scale = 6, precision = 19) private BigDecimal totalWithdrawalFees; + + @Column(name = "total_fees_charge_derived", scale = 6, precision = 19) + private BigDecimal totalFeeCharge; + + @Column(name = "total_penalty_charge_derived", scale = 6, precision = 19) + private BigDecimal totalPenaltyCharge; @Column(name = "total_annual_fees_derived", scale = 6, precision = 19) private BigDecimal totalAnnualFees; @@ -55,9 +61,11 @@ public final class SavingsAccountSummary { this.totalInterestPosted = wrapper.calculateTotalInterestPosted(currency, transactions); this.totalWithdrawalFees = wrapper.calculateTotalWithdrawalFees(currency, transactions); this.totalAnnualFees = wrapper.calculateTotalAnnualFees(currency, transactions); + this.totalFeeCharge = wrapper.calculateTotalFeesCharge(currency, transactions); + this.totalPenaltyCharge = wrapper.calculateTotalPenaltyCharge(currency, transactions); this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) - .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).getAmount(); + .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(totalFeeCharge).minus(totalPenaltyCharge).getAmount(); } public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List allPostingPeriods) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index e4b6fd55d..0c3f0f219 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -7,17 +7,23 @@ package org.mifosplatform.portfolio.savings.domain; import java.math.BigDecimal; import java.util.Date; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; import org.mifosplatform.organisation.monetary.data.CurrencyData; @@ -30,6 +36,7 @@ import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumDat import org.mifosplatform.portfolio.savings.domain.interest.EndOfDayBalance; import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.springframework.data.jpa.domain.AbstractPersistable; +import org.springframework.util.CollectionUtils; /** * All monetary transactions against a savings account are modelled through this @@ -76,7 +83,11 @@ public final class SavingsAccountTransaction extends AbstractPersistable { @Column(name = "balance_number_of_days_derived", nullable = false) private Integer balanceNumberOfDays; - + + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccountTransaction", orphanRemoval = true) + private Set savingsAccountChargesPaid = new HashSet(); + protected SavingsAccountTransaction() { this.dateOf = null; this.typeOf = null; @@ -116,6 +127,20 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount, isReversed); } + + public static SavingsAccountTransaction charge(final SavingsAccount savingsAccount, final Office office, final LocalDate date, + final Money amount) { + final boolean isReversed = false; + return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.APPLY_CHARGES.getValue(), date, amount, + isReversed); + } + + public static SavingsAccountTransaction waiver(final SavingsAccount savingsAccount, final Office office, final LocalDate date, + final Money amount) { + final boolean isReversed = false; + return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WAIVE_CHARGES.getValue(), date, amount, + isReversed); + } public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date) { final boolean isReversed = false; @@ -386,6 +411,40 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public boolean isDebit() { - return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed(); + return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed() || isCharge(); } + + public boolean isCharge() { + return SavingsAccountTransactionType.fromInt(this.typeOf).isCharge(); + } + + public boolean isFeeCharge() { + final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); + return (isCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false; + } + + public boolean isPenaltyCharge() { + final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); + return (isCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false; + } + + public boolean isFeeChargeAndNotReversed(){ + return isFeeCharge() && isNotReversed(); + } + + public boolean isPenaltyChargeAndNotReversed(){ + return isPenaltyCharge() && isNotReversed(); + } + + private SavingsAccountChargePaidBy getSavingsAccountChargePaidBy(){ + if(!CollectionUtils.isEmpty(this.savingsAccountChargesPaid)){ + return this.savingsAccountChargesPaid.iterator().next(); + } + return null; + } + + public Set getSavingsAccountChargesPaid() { + return this.savingsAccountChargesPaid; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java index c5af6eff4..bafe459dc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java @@ -68,4 +68,25 @@ public final class SavingsAccountTransactionSummaryWrapper { } return total.getAmountDefaultedToNullIfZero(); } + + public BigDecimal calculateTotalFeesCharge(final MonetaryCurrency currency, final List transactions) { + Money total = Money.zero(currency); + for (SavingsAccountTransaction transaction : transactions) { + if (transaction.isFeeChargeAndNotReversed() && transaction.isNotReversed()) { + total = total.plus(transaction.getAmount(currency)); + } + } + return total.getAmountDefaultedToNullIfZero(); + } + + public BigDecimal calculateTotalPenaltyCharge(final MonetaryCurrency currency, final List transactions) { + Money total = Money.zero(currency); + for (SavingsAccountTransaction transaction : transactions) { + if (transaction.isPenaltyChargeAndNotReversed() && transaction.isNotReversed()) { + total = total.plus(transaction.getAmount(currency)); + } + } + return total.getAmountDefaultedToNullIfZero(); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java index c1774dc7b..976b9ea66 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java @@ -10,6 +10,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PR import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; @@ -30,13 +31,18 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawal import java.math.BigDecimal; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; import javax.persistence.Table; import org.joda.time.MonthDay; @@ -47,6 +53,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; @@ -55,6 +62,8 @@ import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.springframework.data.jpa.domain.AbstractPersistable; +import com.google.gson.JsonArray; + @Entity @Table(name = "m_savings_product") public class SavingsProduct extends AbstractPersistable { @@ -132,6 +141,10 @@ public class SavingsProduct extends AbstractPersistable { @Column(name = "annual_fee_on_day", nullable = true) private Integer annualFeeOnDay; + + @ManyToMany + @JoinTable(name = "m_savings_product_charge", joinColumns = @JoinColumn(name = "savings_product_id"), inverseJoinColumns = @JoinColumn(name = "charge_id")) + private Set charges; public static SavingsProduct createNew(final String name, final String description, final MonetaryCurrency currency, final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, @@ -139,12 +152,12 @@ public class SavingsProduct extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final boolean withdrawalFeeApplicableForTransfer, - final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType) { + final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType, final Set charges) { return new SavingsProduct(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,withdrawalFeeApplicableForTransfer, annualFeeAmount, - annualFeeOnMonthDay, accountingRuleType); + annualFeeOnMonthDay, accountingRuleType, charges); } protected SavingsProduct() { @@ -158,7 +171,7 @@ public class SavingsProduct extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final boolean withdrawalFeeApplicableForTransfer, - final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType) { + final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType, final Set charges) { this.name = name; this.description = description; @@ -194,6 +207,10 @@ public class SavingsProduct extends AbstractPersistable { this.accountingRule = accountingRuleType.getValue(); } + if (charges != null) { + this.charges = charges; + } + validateLockinDetails(); validateWithdrawalFeeDetails(); validateAnnualFeeDetails(); @@ -424,6 +441,14 @@ public class SavingsProduct extends AbstractPersistable { actualChanges.put(accountingRuleParamName, newValue); this.accountingRule = newValue; } + + //charges + if (command.hasParameter(chargesParamName)) { + JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName); + if (jsonArray != null) { + actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName)); + } + } validateLockinDetails(); validateWithdrawalFeeDetails(); @@ -512,4 +537,23 @@ public class SavingsProduct extends AbstractPersistable { public Integer getAccountingType() { return this.accountingRule; } + + public boolean update(final Set newSavingsProductCharges) { + if (newSavingsProductCharges == null) return false; + + boolean updated = false; + if (this.charges != null) { + final Set currentSetOfCharges = new HashSet(this.charges); + final Set newSetOfCharges = new HashSet(newSavingsProductCharges); + + if (!(currentSetOfCharges.equals(newSetOfCharges))) { + updated = true; + this.charges = newSavingsProductCharges; + } + } else { + updated = true; + this.charges = newSavingsProductCharges; + } + return updated; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java index 25348d780..1df895a36 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java @@ -7,6 +7,9 @@ package org.mifosplatform.portfolio.savings.domain; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; @@ -25,22 +28,39 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawal import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.charge.domain.Charge; +import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.mifosplatform.portfolio.charge.exception.ChargeCannotBeAppliedToException; +import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + @Component public class SavingsProductAssembler { + private final ChargeRepositoryWrapper chargeRepository; + + @Autowired + public SavingsProductAssembler(final ChargeRepositoryWrapper chargeRepository) { + this.chargeRepository = chargeRepository; + } + public SavingsProduct assemble(final JsonCommand command) { final String name = command.stringValueOfParameterNamed(nameParamName); @@ -103,10 +123,46 @@ public class SavingsProductAssembler { final BigDecimal annualFeeAmount = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName); final MonthDay monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule")); - + + //Savings product charges + final Set charges = this.assembleListOfSavingsProductCharges(command, currencyCode); + return SavingsProduct.createNew(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, iswithdrawalFeeApplicableForTransfer, - annualFeeAmount, monthDayOfAnnualFee, accountingRuleType); + annualFeeAmount, monthDayOfAnnualFee, accountingRuleType, charges); + } + + public Set assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) { + + final Set charges = new HashSet(); + + if (command.parameterExists(chargesParamName)) { + JsonArray chargesArray = command.arrayOfParameterNamed(chargesParamName); + if (chargesArray != null) { + for (int i = 0; i < chargesArray.size(); i++) { + + final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject(); + if (jsonObject.has(idParamName)) { + final Long id = jsonObject.get(idParamName).getAsLong(); + + final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id); + + if(!charge.isSavingsCharge()){ + String errorMessage = "Charge with identifier " + charge.getId() + " cannot be applied to Savings product."; + throw new ChargeCannotBeAppliedToException("savings.product", errorMessage, charge.getId()); + } + + if (!savingsProductCurrencyCode.equals(charge.getCurrencyCode())) { + String errorMessage = "Charge and Savings Product must have the same currency."; + throw new InvalidCurrencyException("charge", "attach.to.savings.product", errorMessage); + } + charges.add(charge); + } + } + } + } + + return charges; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductChargeAssembler.java new file mode 100644 index 000000000..d92f793e2 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductChargeAssembler.java @@ -0,0 +1,101 @@ +/** + * 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.domain; + +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeCalculationTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.charge.domain.Charge; +import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; +import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; +import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +@Service +public class SavingsProductChargeAssembler { + + private final FromJsonHelper fromApiJsonHelper; + private final ChargeRepositoryWrapper chargeRepository; + private final SavingsAccountChargeRepository savingsAccountChargeRepository; + + @Autowired + public SavingsProductChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository, + final SavingsAccountChargeRepository savingsAccountChargeRepository) { + this.fromApiJsonHelper = fromApiJsonHelper; + this.chargeRepository = chargeRepository; + this.savingsAccountChargeRepository = savingsAccountChargeRepository; + } + + public Set fromParsedJson(final JsonElement element) { + + final Set savingsAccountCharges = new HashSet(); + + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + + final JsonObject savingsChargeElement = array.get(i).getAsJsonObject(); + + final Long id = fromApiJsonHelper.extractLongNamed(idParamName, savingsChargeElement); + final Long chargeId = fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement); + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale); + final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed(chargeTimeTypeParamName, savingsChargeElement, locale); + final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed(chargeCalculationTypeParamName, savingsChargeElement, + locale); + final LocalDate dueDate = fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement, + dateFormat, locale); + + if (id == null) { + final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId); + ChargeTimeType chargeTime = null; + if (chargeTimeType != null) { + ChargeTimeType.fromInt(chargeTimeType); + } + ChargeCalculationType chargeCalculation = null; + if (chargeCalculationType != null) { + ChargeCalculationType.fromInt(chargeCalculationType); + } + final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(chargeDefinition, amount, chargeTime, chargeCalculation, dueDate); + savingsAccountCharges.add(savingsAccountCharge); + } else { + final Long savingsAccountChargeId = id; + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOne(savingsAccountChargeId); + if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); } + + savingsAccountCharge.update(amount, dueDate); + + savingsAccountCharges.add(savingsAccountCharge); + } + } + } + } + + return savingsAccountCharges; + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/AddSavingsAccountChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/AddSavingsAccountChargeCommandHandler.java new file mode 100644 index 000000000..823010419 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/AddSavingsAccountChargeCommandHandler.java @@ -0,0 +1,32 @@ +/** + * 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.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 AddSavingsAccountChargeCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public AddSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.addSavingsAccountCharge(command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DeleteSavingsAccountChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DeleteSavingsAccountChargeCommandHandler.java new file mode 100644 index 000000000..aba4258cd --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DeleteSavingsAccountChargeCommandHandler.java @@ -0,0 +1,32 @@ +/** + * 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.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 DeleteSavingsAccountChargeCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public DeleteSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.deleteSavingsAccountCharge(command.getSavingsId(), command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/PaySavingsAccountChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/PaySavingsAccountChargeCommandHandler.java new file mode 100644 index 000000000..d8ca1900e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/PaySavingsAccountChargeCommandHandler.java @@ -0,0 +1,32 @@ +/** + * 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.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 PaySavingsAccountChargeCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public PaySavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.payCharge(command.getSavingsId(), command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UpdateSavingsAccountChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UpdateSavingsAccountChargeCommandHandler.java new file mode 100644 index 000000000..334c78111 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UpdateSavingsAccountChargeCommandHandler.java @@ -0,0 +1,32 @@ +/** + * 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.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 UpdateSavingsAccountChargeCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public UpdateSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.updateSavingsAccountCharge(command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java new file mode 100644 index 000000000..e9678a379 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java @@ -0,0 +1,32 @@ +/** + * 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.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 WaiveSavingsAccountChargeCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public WaiveSavingsAccountChargeCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.waiveCharge(command.getSavingsId(), command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java new file mode 100644 index 000000000..6708b95e6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformService.java @@ -0,0 +1,20 @@ +/** + * 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.service; + +import java.util.Collection; + +import org.mifosplatform.portfolio.charge.data.ChargeData; +import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData; + +public interface SavingsAccountChargeReadPlatformService { + + ChargeData retrieveSavingsAccountChargeTemplate(); + + Collection retrieveSavingsAccountCharges(Long savingsAccountId); + + SavingsAccountChargeData retrieveSavingsAccountChargeDetails(Long savingsAccountChargeId, Long savingsAccountId); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java new file mode 100644 index 000000000..41ff245a0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java @@ -0,0 +1,144 @@ +/** + * 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.service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.infrastructure.core.domain.JdbcSupport; +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.charge.data.ChargeData; +import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException; +import org.mifosplatform.portfolio.charge.service.ChargeDropdownReadPlatformService; +import org.mifosplatform.portfolio.charge.service.ChargeEnumerations; +import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccountChargeReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + private final PlatformSecurityContext context; + private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService; + + @Autowired + public SavingsAccountChargeReadPlatformServiceImpl(final PlatformSecurityContext context, + final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource) { + this.context = context; + this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService; + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + private static final class SavingsAccountChargeMapper implements RowMapper { + + public String schema() { + return "sc.id as id, c.id as chargeId, c.name as name, " + + "sc.amount as amountDue, " + + "sc.amount_paid_derived as amountPaid, " + + "sc.amount_waived_derived as amountWaived, " + + "sc.amount_writtenoff_derived as amountWrittenOff, " + + "sc.amount_outstanding_derived as amountOutstanding, " + + "sc.calculation_percentage as percentageOf, sc.calculation_on_amount as amountPercentageAppliedTo, " + + "sc.charge_time_enum as chargeTime, " + + "sc.is_penalty as penalty, " + + "sc.due_for_collection_as_of_date as dueAsOfDate, " + + "sc.charge_calculation_enum as chargeCalculation, " + + "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 " + + "join m_organisation_currency oc on c.currency_code = oc.code " + + "join m_savings_account_charge sc on sc.charge_id = c.id "; + } + + @Override + public SavingsAccountChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong("id"); + final Long chargeId = rs.getLong("chargeId"); + final String name = rs.getString("name"); + final BigDecimal amount = rs.getBigDecimal("amountDue"); + final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid"); + final BigDecimal amountWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWaived"); + final BigDecimal amountWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWrittenOff"); + final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding"); + + final BigDecimal percentageOf = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "percentageOf"); + final BigDecimal amountPercentageAppliedTo = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPercentageAppliedTo"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); + + final int chargeTime = rs.getInt("chargeTime"); + final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime); + + final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate"); + + final int chargeCalculation = rs.getInt("chargeCalculation"); + final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); + final boolean penalty = rs.getBoolean("penalty"); + + final Collection chargeOptions = null; + + return SavingsAccountChargeData.instance(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, chargeOptions, penalty); + } + } + + @Override + public ChargeData retrieveSavingsAccountChargeTemplate() { + this.context.authenticatedUser(); + + final List allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes(); + final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); + + //TODO AA : revisit for merge conflict - Not sure method signature + return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null); + } + + @Override + public SavingsAccountChargeData retrieveSavingsAccountChargeDetails(final Long id, final Long savingsAccountId) { + try { + this.context.authenticatedUser(); + + final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper(); + + final String sql = "select " + rm.schema() + " where sc.id=? and sc.savings_account_id=?"; + + return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { id, savingsAccountId }); + } catch (EmptyResultDataAccessException e) { + throw new SavingsAccountChargeNotFoundException(savingsAccountId); + } + } + + @Override + public Collection retrieveSavingsAccountCharges(final Long loanId) { + this.context.authenticatedUser(); + + final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper(); + + final String sql = "select " + rm.schema() + " where sc.savings_account_id=? " + + " order by sc.charge_time_enum ASC, sc.due_for_collection_as_of_date ASC, sc.is_penalty ASC"; + + return this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 3141c1420..9e1ed2f34 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -27,6 +27,8 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.organisation.staff.service.StaffReadPlatformService; import org.mifosplatform.portfolio.account.data.AccountTransferData; +import org.mifosplatform.portfolio.charge.data.ChargeData; +import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; @@ -40,6 +42,7 @@ import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData; import org.mifosplatform.portfolio.savings.data.SavingsAccountApplicationTimelineData; +import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData; import org.mifosplatform.portfolio.savings.data.SavingsAccountData; import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; import org.mifosplatform.portfolio.savings.data.SavingsAccountSummaryData; @@ -65,6 +68,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead private final SavingsProductReadPlatformService savingsProductReadPlatformService; private final StaffReadPlatformService staffReadPlatformService; private final SavingsDropdownReadPlatformService dropdownReadPlatformService; + private final ChargeReadPlatformService chargeReadPlatformService; // mappers private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper; @@ -79,7 +83,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService, final SavingsProductReadPlatformService savingProductReadPlatformService, - final StaffReadPlatformService staffReadPlatformService, final SavingsDropdownReadPlatformService dropdownReadPlatformService) { + final StaffReadPlatformService staffReadPlatformService, final SavingsDropdownReadPlatformService dropdownReadPlatformService, + final ChargeReadPlatformService chargeReadPlatformService) { this.context = context; this.jdbcTemplate = new JdbcTemplate(dataSource); this.clientReadPlatformService = clientReadPlatformService; @@ -91,6 +96,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead this.transactionsMapper = new SavingsAccountTransactionsMapper(); this.savingAccountMapper = new SavingAccountMapper(); this.annualFeeMapper = new SavingsAccountAnnualFeeMapper(); + this.chargeReadPlatformService = chargeReadPlatformService; } @Override @@ -229,7 +235,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); sqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); - sqlBuilder.append("sa.account_balance_derived as accountBalance "); + sqlBuilder.append("sa.account_balance_derived as accountBalance, "); + sqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, "); + sqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge "); sqlBuilder.append("from m_savings_account sa "); sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); @@ -372,9 +380,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned"); final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestPosted"); final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance"); - + final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge"); + final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge"); + final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, - totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance); + totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, + totalPenaltyCharge); return SavingsAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, @@ -430,6 +441,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Collection withdrawalFeeTypeOptions = this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions(); final Collection transactions = null; + final Collection productCharges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId); + //update charges from Product charges + final Collection charges = fromChargesToSavingsCharges(productCharges); + + final boolean feeChargesOnly = false; + final Collection chargeOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicableCharges(feeChargesOnly); Collection fieldOfficerOptions = null; @@ -459,7 +476,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions); } else { String clientName = null; @@ -483,15 +500,28 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Collection withdrawalFeeTypeOptions = null; final Collection transactions = null; + final Collection charges = null; + + final boolean feeChargesOnly = true; + final Collection chargeOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicableCharges(feeChargesOnly); template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions); } return template; } + private Collection fromChargesToSavingsCharges(Collection productCharges) { + final Collection savingsCharges = new ArrayList(); + for (ChargeData chargeData : productCharges) { + final SavingsAccountChargeData savingsCharge = chargeData.toSavingsAccountChargeData(); + savingsCharges.add(savingsCharge); + } + return savingsCharges; + } + @Override public SavingsAccountTransactionData retrieveDepositTransactionTemplate(final Long savingsId) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java index f6860d3c9..e2c46cf5d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -41,4 +41,14 @@ public interface SavingsAccountWritePlatformService { void rejectSavingsTransfer(Long accountId); SavingsAccountTransaction acceptSavingsTransfer(Long accountId, LocalDate transferDate, Office acceptedInOffice, Staff staff); + + CommandProcessingResult addSavingsAccountCharge(JsonCommand command); + + CommandProcessingResult updateSavingsAccountCharge(JsonCommand command); + + CommandProcessingResult deleteSavingsAccountCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); + + CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); + + CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 92314708e..c5928cbd7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -6,6 +6,8 @@ package org.mifosplatform.portfolio.savings.service; 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; import java.math.BigDecimal; import java.math.MathContext; @@ -40,6 +42,8 @@ import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.portfolio.account.PortfolioAccountType; import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService; import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService; +import org.mifosplatform.portfolio.charge.domain.Charge; +import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.group.domain.Group; @@ -49,10 +53,13 @@ import org.mifosplatform.portfolio.note.domain.NoteRepository; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.mifosplatform.portfolio.savings.SavingsApiConstants; +import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeDataValidator; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDTO; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDataValidator; import org.mifosplatform.portfolio.savings.domain.SavingsAccount; import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountCharge; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountChargeRepositoryWrapper; import org.mifosplatform.portfolio.savings.domain.SavingsAccountDomainService; import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository; import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType; @@ -74,6 +81,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; private final SavingsAccountAssembler savingAccountAssembler; private final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator; + private final SavingsAccountChargeDataValidator savingsAccountChargeDataValidator; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final JournalEntryWritePlatformService journalEntryWritePlatformService; @@ -81,6 +89,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final NoteRepository noteRepository; private final AccountTransfersReadPlatformService accountTransfersReadPlatformService; private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; + private final ChargeRepositoryWrapper chargeRepository; + private final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository; @Autowired public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -88,17 +98,20 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountTransactionRepository savingsAccountTransactionRepository, final SavingsAccountAssembler savingAccountAssembler, final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator, + final SavingsAccountChargeDataValidator savingsAccountChargeDataValidator, final PaymentDetailWritePlatformService paymentDetailWritePlatformService, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final JournalEntryWritePlatformService journalEntryWritePlatformService, final SavingsAccountDomainService savingsAccountDomainService, final NoteRepository noteRepository, final AccountTransfersReadPlatformService accountTransfersReadPlatformService, - final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService) { + final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, + final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository) { this.context = context; this.savingAccountRepository = savingAccountRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; this.savingAccountAssembler = savingAccountAssembler; this.savingsAccountTransactionDataValidator = savingsAccountTransactionDataValidator; + this.savingsAccountChargeDataValidator = savingsAccountChargeDataValidator; this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; @@ -106,6 +119,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.noteRepository = noteRepository; this.accountTransfersReadPlatformService = accountTransfersReadPlatformService; this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService; + this.chargeRepository = chargeRepository; + this.savingsAccountChargeRepository = savingsAccountChargeRepository; } @Transactional @@ -553,4 +568,148 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi return acceptTransferTransaction; } + + @Transactional + @Override + public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command) { + + this.context.authenticatedUser(); + final Long savingsAccountId = command.getSavingsId(); + this.savingsAccountChargeDataValidator.validateAdd(command.json()); + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); + checkClientOrGroupActive(savingsAccount); + + final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); + + final Long chargeDefinitionId = command.longValueOfParameterNamed(chargeIdParamName); + final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId); + + final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command); + + savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition); + + this.savingAccountRepository.saveAndFlush(savingsAccount); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsAccountCharge.getId()) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withLoanId(savingsAccountId) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult updateSavingsAccountCharge(JsonCommand command) { + this.context.authenticatedUser(); + + this.savingsAccountChargeDataValidator.validateUpdate(command.json()); + final Long savingsAccountId = command.getSavingsId(); + final Long savingsChargeId = command.entityId();//SavingsAccount Charge entity + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); + checkClientOrGroupActive(savingsAccount); + + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(savingsChargeId, savingsAccountId); + final Map changes = savingsAccountCharge.update(command); + this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge); + //TODO AA: revisit the code + /* + // Charges may be edited only when the loan associated with them are + // yet to be approved (are in submitted and pending status) + if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeUpdatedException( + LOAN_CHARGE_CANNOT_BE_UPDATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); } +*/ + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsChargeId) // + //.withOfficeId(savings) // + //.withClientId(loan.getClientId()) // + //.withGroupId(loan.getGroupId()) // + //.withLoanId(savingsAccountId) // + .with(changes) // + .build(); + } + + + @Transactional + @Override + public CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command) { + + this.context.authenticatedUser(); + + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); + checkClientOrGroupActive(savingsAccount); + + savingsAccount.waiveCharge(savingsAccountChargeId, existingTransactionIds, existingReversedTransactionIds); + + this.savingAccountRepository.saveAndFlush(savingsAccount); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsAccountChargeId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsAccountId) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult deleteSavingsAccountCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command) { + this.context.authenticatedUser(); + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); + checkClientOrGroupActive(savingsAccount); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); + + //TODO AA: validate before deleting a charge + + savingsAccount.removeCharge(savingsAccountCharge); + this.savingAccountRepository.saveAndFlush(savingsAccount); + + return new CommandProcessingResultBuilder() // + .withEntityId(savingsAccountChargeId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsAccountId) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command) { + this.context.authenticatedUser(); + + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); + checkClientOrGroupActive(savingsAccount); + + final BigDecimal chargeAmount = command.bigDecimalValueOfParameterNamed(amountParamName); + savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, existingTransactionIds, existingReversedTransactionIds); + + this.savingAccountRepository.saveAndFlush(savingsAccount); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsAccountChargeId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsAccountId) // + .build(); + + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index acde05eb2..b7431eff0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; @@ -28,6 +29,7 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyReposit import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper; +import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; import org.mifosplatform.portfolio.client.domain.Client; @@ -44,6 +46,8 @@ import org.mifosplatform.portfolio.savings.SavingsApiConstants; import org.mifosplatform.portfolio.savings.data.SavingsAccountDataValidator; import org.mifosplatform.portfolio.savings.domain.SavingsAccount; import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountCharge; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountChargeAssembler; import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepositoryWrapper; import org.mifosplatform.portfolio.savings.domain.SavingsProduct; import org.mifosplatform.portfolio.savings.domain.SavingsProductRepository; @@ -74,6 +78,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl private final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final JournalEntryWritePlatformService journalEntryWritePlatformService; + private final SavingsAccountChargeAssembler savingsAccountChargeAssembler; @Autowired public SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -84,7 +89,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final NoteRepository noteRepository, final StaffRepositoryWrapper staffRepository, final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, - final JournalEntryWritePlatformService journalEntryWritePlatformService) { + final JournalEntryWritePlatformService journalEntryWritePlatformService, + final SavingsAccountChargeAssembler savingsAccountChargeAssembler) { this.context = context; this.savingAccountRepository = savingAccountRepository; this.savingAccountAssembler = savingAccountAssembler; @@ -98,6 +104,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl this.savingsAccountApplicationTransitionApiJsonValidator = savingsAccountApplicationTransitionApiJsonValidator; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; + this.savingsAccountChargeAssembler = savingsAccountChargeAssembler; } /* @@ -225,6 +232,15 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl } account.update(fieldOfficer); } + + if (changes.containsKey("charges")) { + //final Set savingsProductCharges = this.savi.assembleListOfSavingsProductCharges(command, product.currency().getCode()); + final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson()); + boolean updated = account.update(charges); + if (!updated) { + changes.remove("charges"); + } + } this.savingAccountRepository.saveAndFlush(account); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java index 630714b5d..59b45ed00 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java @@ -101,6 +101,13 @@ public class SavingsEnumerations { SavingsAccountTransactionType.WITHDRAW_TRANSFER.getCode(), "Transfer Withdrawn"); break; default: + case APPLY_CHARGES: + optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.APPLY_CHARGES.getValue().longValue(), + SavingsAccountTransactionType.APPLY_CHARGES.getCode(), "Apply Charge"); + break; + case WAIVE_CHARGES: + optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WAIVE_CHARGES.getValue().longValue(), + SavingsAccountTransactionType.WAIVE_CHARGES.getCode(), "Waive Charge"); break; } return optionData; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java index 371d9c315..a273c7cdd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java @@ -17,6 +17,7 @@ import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.savings.data.SavingsProductData; import org.mifosplatform.portfolio.savings.exception.SavingsProductNotFoundException; import org.springframework.beans.factory.annotation.Autowired; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java index e25bdb565..7dddf9fec 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java @@ -6,8 +6,10 @@ package org.mifosplatform.portfolio.savings.service; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import java.util.Map; +import java.util.Set; import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.mifosplatform.infrastructure.core.api.JsonCommand; @@ -15,6 +17,7 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.savings.data.SavingsProductDataValidator; import org.mifosplatform.portfolio.savings.domain.SavingsProduct; import org.mifosplatform.portfolio.savings.domain.SavingsProductAssembler; @@ -109,6 +112,14 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi final Map changes = product.update(command); + if (changes.containsKey(chargesParamName)) { + final Set savingsProductCharges = this.savingsProductAssembler.assembleListOfSavingsProductCharges(command, product.currency().getCode()); + boolean updated = product.update(savingsProductCharges); + if (!updated) { + changes.remove(chargesParamName); + } + } + // accounting related changes boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName); final Map accountingMappingChanges = accountMappingWritePlatformService.updateSavingsProductToGLAccountMapping( @@ -142,4 +153,5 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi .withEntityId(product.getId()) // .build(); } + } \ No newline at end of file