From 28f0c80b848a27108d9080a72ceae29737cd4d0c Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Wed, 17 Jul 2013 12:45:09 +0100 Subject: [PATCH] MIFOSX-406: fix application modification and update api docs for application process areas. --- api-docs/apiLive.htm | 627 ++++++++++++++---- .../savings/SavingsApiConstants.java | 1 + .../data/SavingsAccountDataValidator.java | 9 +- .../savings/domain/SavingsAccount.java | 54 +- ...WritePlatformServiceJpaRepositoryImpl.java | 31 +- 5 files changed, 580 insertions(+), 142 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index d5b485fc5..75227bd99 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -10175,7 +10175,7 @@ Content-Type: application/json

Savings Product:

An MFIs savings product offerings are modeled using this API.

-

When creating savings accounts, the details from the savings product are used to auto fill details of savings account.

+

When creating savings accounts, the details from the savings product are used to auto fill details of the savings account application process.

@@ -10195,7 +10195,10 @@ Content-Type: application/json - + + + + @@ -11008,7 +11011,50 @@ Content-Type: application/json

Savings Account:

-

An instance of a savings account for a individual or group.

+

Savings accounts are instances of a praticular savings product created for an individual or group. An application process around the creation of accounts is also supported.

+ +
Field Descriptions
The default interest rate set when creating savings accounts of this type of product. e.g. 5% Per year - It number here is always expressed as the Nominal APR.
interestCompoundingPeriodType
The period at which interest rate is compounded. 1=Daily, 2=Weekly (at end of week, sunday), 3=Every two weeks (at end of two weeks, sunday), 4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec), 6=Bi-Annually (30th June, 31st Dec), 7=Annually (31st Dec), 8=No Compounding - Simple Interest
The period at which interest rate is compounded. 1=Daily, 4=Monthly (at end of month)
interestPostingPeriodType
The period at which interest rate is posted or credited to savings account. 4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec)
interestCalculationType
The interest calculation method used: 1=Daily Balance or 2=Average Daily Balance
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Starting StateActionResultant State
-SubmitSubmitted and pending approval
Submitted and pending approvalRejectClosed - Rejected
Submitted and pending approvalWithdrawn by ApplicantClosed - Withdrawn by Applicant
Submitted and pending approvalApproveApproved
ApprovedUndo ApprovalSubmitted and pending approval
ApprovedActivateActive
+
@@ -11025,19 +11071,19 @@ Content-Type: application/json - + - - - - - + + - - + + + + + @@ -11103,11 +11149,11 @@ Content-Type: application/json { "clientId": 1, - "clientName": "Client A AB", + "clientName": "small business", "productOptions": [ { "id": 1, - "name": "First" + "name": "Passbook Savings" } ] } @@ -11119,9 +11165,10 @@ Content-Type: application/json { "clientId": 1, - "clientName": "John Doe", - "savingsProductId": 3, + "clientName": "small business", + "savingsProductId": 1, "savingsProductName": "Passbook Savings", + "timeline": {}, "currency": { "code": "USD", "name": "US Dollar", @@ -11136,6 +11183,11 @@ Content-Type: application/json "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily", "value": "Daily" }, + "interestPostingPeriodType": { + "id": 4, + "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly", + "value": "Monthly" + }, "interestCalculationType": { "id": 1, "code": "savingsInterestCalculationType.dailybalance", @@ -11149,15 +11201,27 @@ Content-Type: application/json "productOptions": [ { "id": 1, - "name": "01-Compounding Daily" - }, - { - "id": 2, - "name": "02-Compounding Monthly" - }, + "name": "Passbook Savings" + } + ], + "fieldOfficerOptions": [ { "id": 3, - "name": "Passbook Savings" + "firstname": "Mrs.", + "lastname": "loanofficerB1", + "displayName": "loanofficerB1, Mrs.", + "officeId": 2, + "officeName": "branch 1", + "isLoanOfficer": true + }, + { + "id": 1, + "firstname": "Mr.", + "lastname": "loanofficerHO", + "displayName": "loanofficerHO, Mr.", + "officeId": 1, + "officeName": "branch 1", + "isLoanOfficer": true } ], "interestCompoundingPeriodTypeOptions": [ @@ -11166,40 +11230,27 @@ Content-Type: application/json "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily", "value": "Daily" }, - { - "id": 2, - "code": "savings.interest.period.savingsCompoundingInterestPeriodType.weekly", - "value": "Weekly" - }, - { - "id": 3, - "code": "savings.interest.period.savingsCompoundingInterestPeriodType.biweekly", - "value": "Bi-Weekly" - }, { "id": 4, "code": "savings.interest.period.savingsCompoundingInterestPeriodType.monthly", "value": "Monthly" + } + ], + "interestPostingPeriodTypeOptions": [ + { + "id": 4, + "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly", + "value": "Monthly" }, { "id": 5, - "code": "savings.interest.period.savingsCompoundingInterestPeriodType.quarterly", + "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.quarterly", "value": "Quarterly" }, - { - "id": 6, - "code": "savings.interest.period.savingsCompoundingInterestPeriodType.biannual", - "value": "Semi-Annual" - }, { "id": 7, - "code": "savings.interest.period.savingsCompoundingInterestPeriodType.annual", + "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.annual", "value": "Annually" - }, - { - "id": 8, - "code": "savings.interest.period.savingsCompoundingInterestPeriodType.nocompounding", - "value": "No Compounding - Simple Interest" } ], "interestCalculationTypeOptions": [ @@ -11247,6 +11298,18 @@ Content-Type: application/json "code": "savings.lockin.savingsPeriodFrequencyType.years", "value": "Years" } + ], + "withdrawalFeeTypeOptions": [ + { + "id": 1, + "code": "savingsWithdrawalFeesType.flat", + "value": "Flat" + }, + { + "id": 2, + "code": "savingsWithdrawalFeesType.percent.of.amount", + "value": "% of Amount" + } ] } @@ -11256,23 +11319,21 @@ Content-Type: application/json  
-

Create a New Savings Account

+

Submit new savings application


Field Descriptions
The account no. associated with this loan. Is auto generated if not provided at creation time.
externalId
A place to put an external reference for this savings account e.g. The ID another system uses. If provided, it must be unique.
A place to put an external reference for this savings account useful in migrations e.g. The ID another system uses. If provided, it must be unique.
active
Indicates whether this savings account should go into active state or not automatically
activationDate
If active is true the activationDate must be provided. If the savings account has an entry for minRequiredOpeningBalance then a deposit for that amount with activationDate as the date is automatically entered.
submittedOnDate
submittedOnDate must be provided when initially creating savings account application. locale and dateFormat parameters must be provided with this.
nominalAnnualInterestRate
The interest rate set for savings account e.g. 5% Per year - It is always expressed as the Nominal APR.
interestCompoundingPeriodType
The period at which interest rate is compounded. 1=Daily, 2=Weekly (at end of week, sunday), 3=Every two weeks (at end of two weeks, sunday), 4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec), 6=Bi-Annually (30th June, 31st Dec), 7=Annually (31st Dec), 8=No Compounding - Simple Interest
interestCompoundingPeriodType
The period at which interest rate is compounded. 1=Daily, 4=Monthly (at end of month)
interestPostingPeriodType
The period at which interest rate is posted or credited to savings account. The actual crediting or posting transaction is date as occurring on the day after the end of the period. 4=Monthly (at end of month), 5=Quarterly (at end of quarter, 31st Mar, 30th Jun, 30th Sep, 31st Dec), 7=Annually (at end of calendar year 31st Dec)
interestCalculationType
The interest calculation method used: 1=Daily Balance or 2=Average Daily Balance
- +
Mandatory Fields
clientId or groupId, productId, accountNo, externalId, - active, (activationDate if active is true)
clientId or groupId, productId, submittedOnDate

- +
Optional Fields
accountNo, externalId
accountNo, externalId, fieldOfficerId

- +
Inherited from Product (if not provided)
nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay
nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay
@@ -11285,7 +11346,9 @@ Request Body: { "clientId": 1, "productId": 1, - "active": false + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "submittedOnDate": "01 March 2011" }

Minimal request: accountNo provided (must be unique), remaining details inherited from savings product.

@@ -11295,7 +11358,9 @@ Request Body: { "clientId": 1, "productId": 1, - "active": false, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "submittedOnDate": "01 March 2011", "accountNo": "SA000023", "externalId": "SYS-23" } @@ -11307,14 +11372,17 @@ Request Body: { "clientId": 1, "productId": 1, + "fieldOfficerId": 1, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "submittedOnDate": "01 March 2013", "accountNo": "SA000023", "externalId": "SYS-23", - "active": true, - "locale": "en", - "dateFormat": "dd MMMM yyyy", - "activationDate": "01 March 2011", - "interestRate": "5.65", - "interestRatePeriodFrequencyType": 2, + "nominalAnnualInterestRate": "5.65", + "interestCompoundingPeriodType": 1, + "interestPostingPeriodType": 4, + "interestCalculationType": 1, + "interestCalculationDaysInYearType": 365, "minRequiredOpeningBalance": "1,000", "lockinPeriodFrequency": 6, "lockinPeriodFrequencyType": 2 @@ -11322,7 +11390,7 @@ Request Body: { - "officeId": 1, + "officeId": 2, "clientId": 1, "savingsId": 1, "resourceId": 1 @@ -11331,11 +11399,184 @@ Request Body: +   +
+
+

Approve savings application

+

Approves savings application so long as its in 'Submitted and pending approval' state.

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=approve + POST savingsaccount/1?command=approve +Content-Type: application/json +Request Body: +{ + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "approvedOnDate": "01 March 2013" +} + + +{ + "officeId": 2, + "clientId": 1, + "savingsId": 1, + "resourceId": 1, + "changes": { + "status": { + "id": 200, + "code": "savingsAccountStatusType.approved", + "value": "Approved", + "submittedAndPendingApproval": false, + "approved": true, + "rejected": false, + "withdrawnByApplicant": false, + "active": false, + "closed": false + }, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "approvedOnDate": "02 March 2013" + } +} + +
+
+ +   +
+
+

Undo approval savings application

+

Will move 'approved' savings application back to 'Submitted and pending approval' state.

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=undoApproval + POST savingsaccount/1?command=undoApproval +Content-Type: application/json +Request Body: +{ +} + + +{ + "officeId": 2, + "clientId": 1, + "savingsId": 1, + "resourceId": 1, + "changes": { + "status": { + "id": 100, + "code": "savingsAccountStatusType.submitted.and.pending.approval", + "value": "Submitted and pending approval", + "submittedAndPendingApproval": true, + "approved": false, + "rejected": false, + "withdrawnByApplicant": false, + "active": false, + "closed": false + }, + "approvedOnDate": "" + } +} + +
+
+ +   +
+
+

Reject savings application

+

Rejects savings application so long as its in 'Submitted and pending approval' state.

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=reject + POST savingsaccount/1?command=reject +Content-Type: application/json +Request Body: +{ + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "rejectedOnDate": "03 March 2013" +} + + +{ + "officeId": 2, + "clientId": 1, + "savingsId": 1, + "resourceId": 1, + "changes": { + "status": { + "id": 500, + "code": "savingsAccountStatusType.rejected", + "value": "Rejected", + "submittedAndPendingApproval": false, + "approved": false, + "rejected": true, + "withdrawnByApplicant": false, + "active": false, + "closed": true + }, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "rejectedOnDate": "03 March 2013", + "closedOnDate": "03 March 2013" + } +} + +
+
+ +   +
+
+

Withdraw savings application

+

Used when an applicant withdraws from the savings application. It must be in 'Submitted and pending approval' state.

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=withdrawnByApplicant + POST savingsaccount/1?command=withdrawnByApplicant +Content-Type: application/json +Request Body: +{ + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "withdrawnOnDate": "03 March 2013" +} + + +{ + "officeId": 2, + "clientId": 1, + "savingsId": 1, + "resourceId": 1, + "changes": { + "status": { + "id": 400, + "code": "savingsAccountStatusType.withdrawn.by.applicant", + "value": "Withdrawn by applicant", + "submittedAndPendingApproval": false, + "approved": false, + "rejected": false, + "withdrawnByApplicant": true, + "active": false, + "closed": true + }, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "withdrawnOnDate": "03 March 2013", + "closedOnDate": "03 March 2013" + } +} + +
+
+  
-

Activate a Savings Account

-

Activates a savings account.

+

Activate a savings account

+

Results in an approved savings application being converted into an 'active' savings account.

POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=activate @@ -11345,15 +11586,31 @@ Request Body: { "locale": "en", "dateFormat": "dd MMMM yyyy", - "activationDate": "01 March 2011" + "activatedOnDate": "01 March 2013" } { - "officeId": 1, + "officeId": 2, "clientId": 1, "savingsId": 1, - "resourceId": 1 + "resourceId": 1, + "changes": { + "status": { + "id": 300, + "code": "savingsAccountStatusType.active", + "value": "Active", + "submittedAndPendingApproval": false, + "approved": false, + "rejected": false, + "withdrawnByApplicant": false, + "active": true, + "closed": false + }, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "activatedOnDate": "01 March 2013" + } }
@@ -11416,7 +11673,7 @@ Request Body:  
-

List Savings Accounts

+

List savings applications/accounts

Example Requests:

savingsaccounts


@@ -11425,40 +11682,78 @@ Request Body:
GET https://Domain Name/api/v1/savingsaccounts -[ - { - "id": 1, - "accountNo": "SA000023", - "externalId": "SYS-23", - "groupId": 0, - "clientId": 1, - "clientName": "Client A AB", - "savingsProductId": 1, - "savingsProductName": "First", - "currency": { - "code": "USD", - "name": "US Dollar", - "decimalPlaces": 2, - "displaySymbol": "$", - "nameCode": "currency.USD", - "displayLabel": "US Dollar ($)" - }, - "interestRate": 5.65, - "interestRatePeriodFrequencyType": { - "id": 2, - "code": "savings.interest.rate.periodFrequencyType.months", - "value": "Per month" - }, - "annualInterestRate": 67.8, - "minRequiredOpeningBalance": 1000, - "lockinPeriodFrequency": 6, - "lockinPeriodFrequencyType": { - "id": 2, - "code": "savings.interest.rate.periodFrequencyType.months", - "value": "Per month" +{ + "totalFilteredRecords": 1, + "pageItems": [ + { + "id": 1, + "accountNo": "000000001", + "clientId": 1, + "clientName": "small business", + "savingsProductId": 1, + "savingsProductName": "Passbook Savings", + "fieldOfficerId": 0, + "status": { + "id": 100, + "code": "savingsAccountStatusType.submitted.and.pending.approval", + "value": "Submitted and pending approval", + "submittedAndPendingApproval": true, + "approved": false, + "rejected": false, + "withdrawnByApplicant": false, + "active": false, + "closed": false + }, + "timeline": { + "submittedOnDate": [ + 2013, + 3, + 1 + ] + }, + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "nominalAnnualInterestRate": 5, + "interestCompoundingPeriodType": { + "id": 1, + "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily", + "value": "Daily" + }, + "interestPostingPeriodType": { + "id": 4, + "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly", + "value": "Monthly" + }, + "interestCalculationType": { + "id": 1, + "code": "savingsInterestCalculationType.dailybalance", + "value": "Daily Balance" + }, + "interestCalculationDaysInYearType": { + "id": 365, + "code": "savingsInterestCalculationDaysInYearType.days365", + "value": "365 Days" + }, + "summary": { + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "accountBalance": 0 + } } - } -] + ] +}
@@ -11466,7 +11761,7 @@ Request Body:  
-

Retrieve a Savings Account:

+

Retrieve a savings application/account:

Arguments

associations
@@ -11485,12 +11780,30 @@ Request Body: { "id": 1, - "accountNo": "000000002", - "groupId": 0, + "accountNo": "000000001", "clientId": 1, - "clientName": "Test b Client", + "clientName": "small business", "savingsProductId": 1, - "savingsProductName": "individual savings", + "savingsProductName": "Passbook Savings", + "fieldOfficerId": 0, + "status": { + "id": 100, + "code": "savingsAccountStatusType.submitted.and.pending.approval", + "value": "Submitted and pending approval", + "submittedAndPendingApproval": true, + "approved": false, + "rejected": false, + "withdrawnByApplicant": false, + "active": false, + "closed": false + }, + "timeline": { + "submittedOnDate": [ + 2013, + 3, + 1 + ] + }, "currency": { "code": "USD", "name": "US Dollar", @@ -11499,13 +11812,27 @@ Request Body: "nameCode": "currency.USD", "displayLabel": "US Dollar ($)" }, - "interestRate": 9.8, - "interestRatePeriodFrequencyType": { - "id": 3, - "code": "savings.interest.rate.periodFrequencyType.years", - "value": "Per year" + "nominalAnnualInterestRate": 5, + "interestCompoundingPeriodType": { + "id": 1, + "code": "savings.interest.period.savingsCompoundingInterestPeriodType.daily", + "value": "Daily" + }, + "interestPostingPeriodType": { + "id": 4, + "code": "savings.interest.posting.period.savingsPostingInterestPeriodType.monthly", + "value": "Monthly" + }, + "interestCalculationType": { + "id": 1, + "code": "savingsInterestCalculationType.dailybalance", + "value": "Daily Balance" + }, + "interestCalculationDaysInYearType": { + "id": 365, + "code": "savingsInterestCalculationDaysInYearType.days365", + "value": "365 Days" }, - "annualInterestRate": 9.8, "summary": { "currency": { "code": "USD", @@ -11515,9 +11842,7 @@ Request Body: "nameCode": "currency.USD", "displayLabel": "US Dollar ($)" }, - "totalDeposits": 456.67, - "totalWithdrawals": 173, - "accountBalance": 283.67 + "accountBalance": 0 } } @@ -11527,9 +11852,8 @@ Request Body:  
-

Update a Savings Account

-

Will most likely have a restriction to only allow 'inactive' savings accounts to be updated.

-

As other updates like change of interest rate are possibly special cases for savings.

+

Modify a savings application

+

Savings application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc

PUT https://Domain Name/api/v1/savingsaccounts/{accountsId} @@ -11538,16 +11862,17 @@ Content-Type: application/json No Request Body: { "locale": "en", - "interestRate": "5.2323" + "nominalAnnualInterestRate": "5.65", } { - "officeId": 1, + "officeId": 2, "clientId": 1, + "savingsId": 1, "resourceId": 1, "changes": { - "interestRate": 5.2323, + "nominalAnnualInterestRate": 5.9999999999, "locale": "en" } } @@ -11558,8 +11883,8 @@ No Request Body:  
-

Delete a Savings Account

-

At present we support hard delete of savings account but this is likely to change to a savings account closure or De-activation process/command.

+

Delete a savings application

+

At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.

DELETE https://Domain Name/api/v1/savingsaccounts/{accountsId} @@ -16786,15 +17111,15 @@ GET https://DomainName/api/v1/search?query=000000111 savingsproducts/{productId} - Retrieve a Savings product - Update a Savings product - Delete a Savings product + Retrieve a savings product + Update a savings product + Delete a savings product Savings Accounts savingsaccounts - Create new Savings Account - List Savings Accounts + Submit new savings application + List savings application/accounts @@ -16802,7 +17127,7 @@ GET https://DomainName/api/v1/search?query=000000111 savingsaccounts/template?clientId={clientId} - Retrieve Savings Account Template + Retrieve savings template @@ -16810,14 +17135,46 @@ GET https://DomainName/api/v1/search?query=000000111 savingsaccounts/{accountId} - Retrieve a Savings Account - Update a Savings Account - Delete a Savings Account + Retrieve a savings application/account + Modify a savings application + Delete a savings application + + + + savingsaccounts/{accountId}?command=approve + Approve a savings application + + + + + + + savingsaccounts/{accountId}?command=undoApproval + Undo savings application approval + + + + + + + savingsaccounts/{accountId}?command=reject + Reject a savings application + + + + + + + savingsaccounts/{accountId}?command=withdraw + Withdraw savings application + + + savingsaccounts/{accountId}?command=activate - Activate a Savings Account + Activate a savings account @@ -16825,7 +17182,7 @@ GET https://DomainName/api/v1/search?query=000000111 savingsaccounts/{accountId}?command=calculateInterest - Calculate Interest on a Savings Account + Calculate interest on a savings account @@ -16833,7 +17190,7 @@ GET https://DomainName/api/v1/search?query=000000111 savingsaccounts/{accountId}?command=postInterest - Post Interest on a Savings Account + Post interest on a savings account @@ -16842,14 +17199,14 @@ GET https://DomainName/api/v1/search?query=000000111 savingsaccounts/{accountId}/transactions/template - Retrieve Savings Account Transaction Template + Retrieve savings account transaction template Savings Transactions savingsaccounts/{accountId}/transactions?command=deposit - Make a Deposit + Make a deposit @@ -16857,7 +17214,7 @@ GET https://DomainName/api/v1/search?query=000000111 savingsaccounts/{accountId}/transactions?command=withdrawal - Make a Withdrawal + Make a withdrawal 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 cd0746793..baa0f9d04 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 @@ -27,6 +27,7 @@ public class SavingsApiConstants { public static String withdrawnByApplicantAction = ".withdrawnByApplicant"; public static String activateAction = ".activate"; public static String modifyApplicationAction = ".modify"; + public static String deleteApplicationAction = ".delete"; // general public static final String localeParamName = "locale"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java index 64ea723eb..45f5842d1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java @@ -85,9 +85,7 @@ public class SavingsAccountDataValidator { baseDataValidator.reset().parameter(productIdParamName).value(productId).notNull().integerGreaterThanZero(); final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element); - if (submittedOnDate == null) { - baseDataValidator.reset().parameter(submittedOnDateParamName).value(submittedOnDate).notNull(); - } + baseDataValidator.reset().parameter(submittedOnDateParamName).value(submittedOnDate).notNull(); if (fromApiJsonHelper.parameterExists(accountNoParamName, element)) { final String accountNo = fromApiJsonHelper.extractStringNamed(accountNoParamName, element); @@ -264,6 +262,11 @@ public class SavingsAccountDataValidator { baseDataValidator.reset().parameter(productIdParamName).value(productId).notNull().integerGreaterThanZero(); } + if (fromApiJsonHelper.parameterExists(submittedOnDateParamName, element)) { + final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element); + baseDataValidator.reset().parameter(submittedOnDateParamName).value(submittedOnDate).notNull(); + } + if (fromApiJsonHelper.parameterExists(accountNoParamName, element)) { final String accountNo = fromApiJsonHelper.extractStringNamed(accountNoParamName, element); baseDataValidator.reset().parameter(accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20); 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 9c7d1ff26..380a0d114 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 @@ -98,7 +98,6 @@ public class SavingsAccount extends AbstractPersistable { @JoinColumn(name = "product_id", nullable = false) private SavingsProduct product; - @SuppressWarnings("unused") @ManyToOne @JoinColumn(name = "field_officer_id", nullable = true) private Staff fieldOfficer; @@ -343,6 +342,14 @@ public class SavingsAccount extends AbstractPersistable { return SavingsAccountStatusType.fromInt(this.status).isActive(); } + public boolean isNotSubmittedAndPendingApproval() { + return !isSubmittedAndPendingApproval(); + } + + public boolean isSubmittedAndPendingApproval() { + return SavingsAccountStatusType.fromInt(this.status).isSubmittedAndPendingApproval(); + } + public void postInterest(final SavingsHelper savingsHelper, final MathContext mc, final LocalDate interestPostingUpToDate, final List existingTransactionIds, final List existingReversedTransactionIds) { @@ -876,6 +883,11 @@ public class SavingsAccount extends AbstractPersistable { actualChanges.put(SavingsApiConstants.productIdParamName, newValue); } + if (command.isChangeInLongParameterNamed(SavingsApiConstants.fieldOfficerIdParamName, fieldOfficerId())) { + final Long newValue = command.longValueOfParameterNamed(SavingsApiConstants.fieldOfficerIdParamName); + actualChanges.put(SavingsApiConstants.fieldOfficerIdParamName, newValue); + } + if (command.isChangeInBigDecimalParameterNamed(SavingsApiConstants.nominalAnnualInterestRateParamName, this.nominalAnnualInterestRate)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.nominalAnnualInterestRateParamName); @@ -884,6 +896,34 @@ public class SavingsAccount extends AbstractPersistable { this.nominalAnnualInterestRate = newValue; } + if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.interestCompoundingPeriodTypeParamName, + this.interestCompoundingPeriodType)) { + final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.interestCompoundingPeriodTypeParamName); + this.interestCompoundingPeriodType = newValue != null ? SavingsCompoundingInterestPeriodType.fromInt(newValue).getValue() + : newValue; + actualChanges.put(SavingsApiConstants.interestCompoundingPeriodTypeParamName, this.interestCompoundingPeriodType); + } + + if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.interestPostingPeriodTypeParamName, this.interestPostingPeriodType)) { + final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.interestPostingPeriodTypeParamName); + this.interestPostingPeriodType = newValue != null ? SavingsPostingInterestPeriodType.fromInt(newValue).getValue() : newValue; + actualChanges.put(SavingsApiConstants.interestPostingPeriodTypeParamName, this.interestPostingPeriodType); + } + + if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.interestCalculationTypeParamName, this.interestCalculationType)) { + final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.interestCalculationTypeParamName); + this.interestCalculationType = newValue != null ? SavingsInterestCalculationType.fromInt(newValue).getValue() : newValue; + actualChanges.put(SavingsApiConstants.interestCalculationTypeParamName, this.interestCalculationType); + } + + if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.interestCalculationDaysInYearTypeParamName, + this.interestCalculationDaysInYearType)) { + final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.interestCalculationDaysInYearTypeParamName); + this.interestCalculationDaysInYearType = newValue != null ? SavingsInterestCalculationDaysInYearType.fromInt(newValue) + .getValue() : newValue; + actualChanges.put(SavingsApiConstants.interestCalculationDaysInYearTypeParamName, this.interestCalculationDaysInYearType); + } + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(SavingsApiConstants.minRequiredOpeningBalanceParamName, this.minRequiredOpeningBalance)) { final BigDecimal newValue = command @@ -1098,6 +1138,10 @@ public class SavingsAccount extends AbstractPersistable { this.product = product; } + public void update(final Staff fieldOfficer) { + this.fieldOfficer = fieldOfficer; + } + public void updateAccountNo(final String newAccountNo) { this.accountNumber = newAccountNo; this.accountNumberRequiresAutoGeneration = false; @@ -1145,6 +1189,14 @@ public class SavingsAccount extends AbstractPersistable { return id; } + public Long fieldOfficerId() { + Long id = null; + if (this.fieldOfficer != null) { + id = this.fieldOfficer.getId(); + } + return id; + } + public MonetaryCurrency getCurrency() { return this.currency; } 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 ed5bd7846..213580cd3 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 @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.savings.service; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -13,14 +15,19 @@ import java.util.Map; import org.apache.commons.lang.StringUtils; import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; 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.client.domain.AccountNumberGenerator; import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; import org.mifosplatform.portfolio.client.domain.Client; @@ -60,6 +67,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl private final GroupRepository groupRepository; private final SavingsProductRepository savingsProductRepository; private final NoteRepository noteRepository; + private final StaffRepositoryWrapper staffRepository; private final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final JournalEntryWritePlatformService journalEntryWritePlatformService; @@ -70,7 +78,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final SavingsAccountDataValidator savingsAccountDataValidator, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final ClientRepositoryWrapper clientRepository, final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository, - final NoteRepository noteRepository, + final NoteRepository noteRepository, final StaffRepositoryWrapper staffRepository, final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final JournalEntryWritePlatformService journalEntryWritePlatformService) { @@ -83,6 +91,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl this.groupRepository = groupRepository; this.savingsProductRepository = savingsProductRepository; this.noteRepository = noteRepository; + this.staffRepository = staffRepository; this.savingsAccountApplicationTransitionApiJsonValidator = savingsAccountApplicationTransitionApiJsonValidator; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; @@ -198,6 +207,13 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl account.update(product); } + if (changes.containsKey(SavingsApiConstants.fieldOfficerIdParamName)) { + final Long fieldOfficerId = command.longValueOfParameterNamed(SavingsApiConstants.fieldOfficerIdParamName); + final Staff fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId); + + account.update(fieldOfficer); + } + this.savingAccountRepository.saveAndFlush(account); } @@ -220,10 +236,19 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl @Override public CommandProcessingResult deleteApplication(final Long savingsId) { - this.context.authenticatedUser(); - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + if (account.isNotSubmittedAndPendingApproval()) { + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.deleteApplicationAction); + + baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName) + .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state"); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + List relatedNotes = this.noteRepository.findBySavingsAccountId(savingsId); this.noteRepository.deleteInBatch(relatedNotes);