diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index aa683f780..a5e87e3ac 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -90,7 +90,7 @@
-
1.7.0.RELEASE API
+
1.8.0.RELEASE API
-
-
-
-
-
@@ -861,7 +831,7 @@
| Staff |
staff |
- Create a Staff |
+ Create a Staff Member |
List Staff |
|
|
@@ -2166,7 +2136,33 @@ Request Body:
}
-
+
+
+
+
+
+
Transfer a Client
+
Allows you to transfer a Client between Offices.
+
+
+ POST https://Domain Name/api/v1/clients/{clientId}?command=transfer
+ POST clients/1?command=transfer
+Content-Type: application/json
+Request Body:
+{
+ destinationOfficeId:1
+}
+
+
+{
+ "clientId": 2,
+ "resourceId": 2
+}
+
+
+
+
+
@@ -3828,6 +3824,118 @@ Request Body:
+
+
+
+
+
Associate Clients
+
This API allows to associate existing clients to a group.
+
The clients are listed from the office to which the group is associated.
+
If client(s) is already associated with group then API will result in an error.
+
+
+
+ POST https://Domain Name/api/v1/groups/{groupId}?command=associateClients
+ POST groups/1?command=associateClients
+Content-Type: application/json
+Request Body:
+{
+ "clientMembers":[1,2]
+}
+
+
+{
+ "officeId": 1,
+ "groupId": 1,
+ "resourceId": 1,
+ "clientMembers": [1,2]
+}
+
+
+
+
+
+
+
Transfer Clients across groups
+
This API allows to transfer clients from one group to another
+
+
+ Field Descriptions |
+
+
+ | destinationGroupId |
+
+
+ | The identifier of the group to which the clients are to be transferred. |
+
+
+ | clients |
+
+
+ | Identifiers of all clients who need to be transferred
+ |
+
+
+ | inheritDestinationGroupLoanOfficer |
+
+
+ | Flag specifies if the transferred clients (and all their active loans)
+ should be linked to the assigned loan officer for the group
+ |
+
+
+ | transferActiveLoans |
+
+
+ | Flag specifies if transfers should proceed when active loans are encountered.
+ If set to true, the schedules of the actives loans are updated to match that meeting schedule
+ of the destination Group
+ |
+
+
+
+
+
+ Mandatory Fields |
+
+
+ | destinationGroupId, clients |
+
+
+
+
+
+ Optional Fields |
+
+
+ | inheritDestinationGroupLoanOfficer (defaults to true), transferActiveLoans (defaults to true) |
+
+
+
+
+
+
+
+
+
+ POST https://Domain Name/api/v1/groups/{groupId}?command=transferClients
+ POST groups/1?command=transferClients
+Content-Type: application/json
+Request Body:
+{
+ destinationGroupId:2,
+ clients:[{id:1}]
+}
+
+
+{
+ "resourceId": 1
+}
+
+
+
+
+
@@ -3888,6 +3996,233 @@ GET https://DomainName/mifosng-provider/api/v1/groups/1?associations=clientMembe
+
+
+
+
+
Retrieve Group accounts overview
+
+ An example of how a loan portfolio summary can be provided. This
+ is requested in a specific use case of the reference application.
It is quite reasonable to add resources like this to simplify User Interface development.
+
+
Example Requests:
+
groups/1/accounts
+
+
+
groups/1/accounts?fields=loanAccounts,savingsAccounts,memberLoanAccounts,
memberSavingsAccounts
+
+
+
+GET https://DomainName/api/v1/groups/{groupId}/accounts
+
+
+{
+ "loanAccounts": [
+ {
+ "id": 3,
+ "accountNo": "000000003",
+ "productId": 3,
+ "productName": "daily product",
+ "status": {
+ "id": 100,
+ "code": "loanStatusType.submitted.and.pending.approval",
+ "value": "Submitted and pending approval",
+ "pendingApproval": true,
+ "waitingForDisbursal": false,
+ "active": false,
+ "closedObligationsMet": false,
+ "closedWrittenOff": false,
+ "closedRescheduled": false,
+ "closed": false,
+ "overpaid": false
+ },
+ "loanType": {
+ "id": 2,
+ "code": "accountType.group",
+ "value": "Group"
+ }
+ }
+ ],
+ "savingsAccounts": [
+ {
+ "id": 9,
+ "accountNo": "000000009",
+ "productId": 1,
+ "productName": "p_sav",
+ "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
+ },
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "accountType": {
+ "id": 2,
+ "code": "accountType.group",
+ "value": "Group"
+ }
+ },
+ {
+ "id": 4,
+ "accountNo": "000000004",
+ "productId": 1,
+ "productName": "p_sav",
+ "status": {
+ "id": 300,
+ "code": "savingsAccountStatusType.active",
+ "value": "Active",
+ "submittedAndPendingApproval": false,
+ "approved": false,
+ "rejected": false,
+ "withdrawnByApplicant": false,
+ "active": true,
+ "closed": false
+ },
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "accountType": {
+ "id": 2,
+ "code": "accountType.group",
+ "value": "Group"
+ }
+ }
+ ],
+ "memberLoanAccounts": [
+ {
+ "id": 4,
+ "accountNo": "000000004",
+ "productId": 1,
+ "productName": "testLoan",
+ "status": {
+ "id": 200,
+ "code": "loanStatusType.approved",
+ "value": "Approved",
+ "pendingApproval": false,
+ "waitingForDisbursal": true,
+ "active": false,
+ "closedObligationsMet": false,
+ "closedWrittenOff": false,
+ "closedRescheduled": false,
+ "closed": false,
+ "overpaid": false
+ },
+ "loanType": {
+ "id": 3,
+ "code": "accountType.jlg",
+ "value": "JLG"
+ }
+ },
+ {
+ "id": 7,
+ "accountNo": "000000007",
+ "productId": 2,
+ "productName": "weekly product",
+ "status": {
+ "id": 100,
+ "code": "loanStatusType.submitted.and.pending.approval",
+ "value": "Submitted and pending approval",
+ "pendingApproval": true,
+ "waitingForDisbursal": false,
+ "active": false,
+ "closedObligationsMet": false,
+ "closedWrittenOff": false,
+ "closedRescheduled": false,
+ "closed": false,
+ "overpaid": false
+ },
+ "loanType": {
+ "id": 3,
+ "code": "accountType.jlg",
+ "value": "JLG"
+ }
+ }
+ ],
+ "memberSavingsAccounts": [
+ {
+ "id": 3,
+ "accountNo": "000000003",
+ "productId": 1,
+ "productName": "p_sav",
+ "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
+ },
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "accountType": {
+ "id": 3,
+ "code": "accountType.jlg",
+ "value": "JLG"
+ }
+ },
+ {
+ "id": 10,
+ "accountNo": "000000010",
+ "productId": 1,
+ "productName": "p_sav",
+ "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
+ },
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "accountType": {
+ "id": 3,
+ "code": "accountType.jlg",
+ "value": "JLG"
+ }
+ }
+ ]
+}
+
+
+
+
+
@@ -5701,6 +6036,7 @@ GET https://DomainName/api/v1/loans/{loanId}/transactions/template"
"total": {
"currencyCode": "XOF",
"digitsAfterDecimal": 0,
+ "inMultiplesOf": 0,
"amount": 471,
"defaultName": "CFA Franc BCEAO",
"nameCode": "currency.XOF",
@@ -7246,6 +7582,13 @@ Request Body:
| Override the currency default value for digitsAfterDecimal. |
+
+ | inMultiplesOf |
+
+
+ | Override the default value for rounding currency to multiples of value provided. |
+
+
| principal |
@@ -7926,7 +8269,7 @@ GET https://DomainName/api/v1/loanproducts/{productId}
Mandatory Fields |
- | name, currencyCode, digitsAfterDecimal, principal,
+ | name, currencyCode, digitsAfterDecimal, inMultiplesOf, principal,
numberOfRepayments, repaymentEvery, repaymentFrequencyType,
interestRatePerPeriod, interestRateFrequencyType, amortizationType, interestType,
interestCalculationPeriodType, transactionProcessingStrategyId,
@@ -7971,6 +8314,7 @@ Request Body:
"currencyCode": "USD",
"locale": "en_GB",
"digitsAfterDecimal": "2",
+ "inMultiplesOf": 0,
"principal": "100,000.00",
"numberOfRepayments": "12",
"repaymentEvery": "1",
@@ -8000,6 +8344,7 @@ Request Body:
"currencyCode": "USD",
"locale": "en_GB",
"digitsAfterDecimal": "2",
+ "inMultiplesOf": 0,
"principal": "100,000.00",
"numberOfRepayments": "12",
"repaymentEvery": "1",
@@ -8056,6 +8401,7 @@ Request Body:
"currencyCode": "USD",
"locale": "en_GB",
"digitsAfterDecimal": "2",
+ "inMultiplesOf": 0,
"principal": "100,000.00",
"numberOfRepayments": "12",
"repaymentEvery": "1",
@@ -10029,7 +10375,8 @@ Request Body:
"officeId": 1,
"firstname": "John",
"lastname": "Doe",
- "isLoanOfficer": "true"
+ "isLoanOfficer": "true",
+ "externalId": "17H"
}
@@ -10062,7 +10409,8 @@ GET https://DomainName/api/v1/staff
"displayName": "Doe, John",
"officeId": 1,
"officeName": "Head Office",
- "isLoanOfficer": true
+ "isLoanOfficer": true,
+ "externalId": "17H"
}
]
@@ -10087,7 +10435,8 @@ GET https://DomainName/api/v1/staff
"displayName": "Doe, John",
"officeId": 1,
"officeName": "Head Office",
- "isLoanOfficer": true
+ "isLoanOfficer": true,
+ "externalId": "17H"
}
@@ -10108,7 +10457,8 @@ PUT staff/1
Content-Type: application/json
Request Body:
{
- "isLoanOfficer": "false"
+ "isLoanOfficer": "false",
+ "externalId": "17Hbb"
}
@@ -10116,7 +10466,8 @@ Request Body:
"officeId": 1,
"resourceId": 1,
"changes": {
- "isLoanOfficer": false
+ "isLoanOfficer": false,
+ "externalId": "17Hbb"
}
}
@@ -10923,6 +11274,9 @@ Content-Type: application/json
|
| digitsAfterDecimal |
| Override the currency default value for digitsAfterDecimal. |
+
| inMultiplesOf |
+
| Override the default value for rounding currency to multiples of provided value. |
+
| nominalAnnualInterestRate |
| 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. |
@@ -11469,7 +11823,7 @@ Content-Type: application/json
Mandatory Fields |
- | name, description, currencyCode, digitsAfterDecimal, nominalAnnualInterestRate,
+ | name, description, currencyCode, digitsAfterDecimal,inMultiplesOf, nominalAnnualInterestRate,
interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType,accountingRule |
@@ -11496,6 +11850,7 @@ Request Body:
"description": "Daily compounding using Daily Balance, 5% per year, 365 days in year",
"currencyCode": "USD",
"digitsAfterDecimal": 2,
+ "inMultiplesOf": 0,
"locale": "en",
"nominalAnnualInterestRate": "5.0",
"interestCompoundingPeriodType": 1,
@@ -11519,6 +11874,7 @@ Request Body:
"description": "A savings product",
"currencyCode": "USD",
"digitsAfterDecimal": "2",
+ "inMultiplesOf": 0,
"nominalAnnualInterestRate": "15",
"interestCompoundingPeriodType": "1",
"interestPostingPeriodType": "4",
@@ -12651,7 +13007,6 @@ No Request Body:
- savingsaccounts_transactions_template
@@ -12703,6 +13058,70 @@ No Request Body:
+
+
+
+
Retrieve Savings Account Transaction:
+
This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+
+ - Field Defaults
+ - Allowed Value Lists
+
+
+
Example Requests:
+
savingsaccounts/1/transactions/1
+
+
+
+ GET https://Domain Name/api/v1/savingsaccounts/{accountId}/transactions/{transactionId}
+
+
+{
+ "id": 1,
+ "transactionType": {
+ "id": 2,
+ "code": "savingsAccountTransactionType.withdrawal",
+ "value": "Withdrawal",
+ "deposit": false,
+ "withdrawal": true,
+ "interestPosting": false,
+ "feeDeduction": false
+ },
+ "accountId": 1,
+ "accountNo": "000000001",
+ "date": [
+ 2013,
+ 8,
+ 7
+ ],
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "inMultiplesOf": 0,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "paymentDetailData": {
+ "id": 62,
+ "paymentType": {
+ "id": 11,
+ "name": "cash"
+ },
+ "accountNumber": "",
+ "checkNumber": "",
+ "routingCode": "",
+ "receiptNumber": "",
+ "bankNumber": ""
+ },
+ "amount": 5000,
+ "runningBalance": 0,
+ "reversed": true
+}
+
+
+
@@ -12786,6 +13205,49 @@ No Request Body:
+
+
+
+
Adjust Transaction
+
This command modifies the given transaction.
+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{accountsId}/transactions/{transactionId}?command=modify
+ POST savingsaccounts/1/transactions/1?command=modify
+Content-Type: application/json
+No Request Body:
+{
+ "locale": "en",
+ "dateFormat": "dd MMMM yyyy",
+ "transactionDate": "27 May 2013",
+ "transactionAmount": "500",
+ "paymentTypeId": "14",
+ "accountNumber": "acc123",
+ "checkNumber": "che123",
+ "routingCode": "rou123",
+ "receiptNumber": "rec123",
+ "bankNumber": "ban123"
+}
+
+
+{
+ "officeId": 1,
+ "clientId": 2,
+ "savingsId": 1,
+ "resourceId": 48,
+ "changes": {
+ "accountNumber": "acc123",
+ "checkNumber": "che123",
+ "routingCode": "rou123",
+ "receiptNumber": "rec123",
+ "bankNumber": "ban123"
+ }
+}
+
+
+
+
+
@@ -12811,6 +13273,835 @@ No Request Body:
+
+
+
+
+
+
Account Transfers:
+
Ability to be able to transfer monetary funds from one account to another.
+
Note: At present only savings account to savings account transfers are supported.
+
+ Mandatory Parameters |
+
+ | fromOfficeId |
+ | The id of the office/branch from which the transfer is made. |
+
+ | fromClientId |
+ | The id of the client from which the transfer is made. |
+
+ | fromAccountType |
+ | The type of account from which the transfer is made. 1=Loan Account, 2=Savings Account |
+
+ | fromAccountId |
+ | The id of the account from which the transfer is made. |
+
+ | toOfficeId |
+ | The id of the office/branch to which the transfer is made. |
+
+ | toClientId |
+ | The id of the client to which the transfer is made. |
+
+ | toAccountType |
+ | The type of account to which the transfer is made. 1=Loan Account, 2=Savings Account |
+
+ | toAccountId |
+ | The id of the account to which the transfer is made. The accouont must be active and must have the same currency as that of the selected fromAccountId. |
+
+ | transferDate |
+ | The date of the transfer. Requires dateFormat and locale parameters. |
+
+ | transferAmount |
+ | The amount of the transfer. Requires locale parameter. |
+
+ | transferDescription |
+ | Description of the transfer itself. |
+
+
+
+
+
+
+
+
Retrieve Account Transfer Template:
+
This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:
+
+ - Field Defaults
+ - Allowed Value Lists
+
+
+
Example Requests:
+
accounttransfers/template?fromAccountType=2&fromOfficeId=1
+
+
accounttransfers/template?fromAccountType=2&fromOfficeId=1&fromClientId=1
+
+
accounttransfers/template?fromClientId=1&fromAccountType=2&fromAccountId=1
+
+
+
+ GET https://Domain Name/api/v1/accounttransfers/template?fromAccountType=2&fromOfficeId=1
+
+
+{
+ "transferAmount": 0,
+ "transferDate": [
+ 2013,
+ 8,
+ 15
+ ],
+ "fromOffice": {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO",
+ "externalId": "1",
+ "openingDate": [
+ 2009,
+ 1,
+ 1
+ ],
+ "hierarchy": "."
+ },
+ "fromAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "fromOfficeOptions": [
+ {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO"
+ },
+ {
+ "id": 2,
+ "name": "Branch 1",
+ "nameDecorated": "....Branch 1"
+ }
+ ],
+ "fromClientOptions": [
+ {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ {
+ "id": 3,
+ "displayName": "Third client",
+ "officeId": 1,
+ "officeName": "HO"
+ }
+ ],
+ "fromAccountTypeOptions": [
+ {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ }
+ ],
+ "toOfficeOptions": [
+ {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO"
+ },
+ {
+ "id": 2,
+ "name": "Branch 1",
+ "nameDecorated": "....Branch 1"
+ }
+ ],
+ "toAccountTypeOptions": [
+ {
+ "id": 1,
+ "code": "accountType.loan",
+ "value": "Loan Account"
+ },
+ {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ }
+ ]
+}
+
+
+
+
+ GET https://Domain Name/api/v1/accounttransfers/template?fromAccountType=2&fromOfficeId=1&fromClientId=1
+
+
+{
+ "transferAmount": 0,
+ "transferDate": [
+ 2013,
+ 8,
+ 15
+ ],
+ "fromOffice": {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO",
+ "externalId": "1",
+ "openingDate": [
+ 2009,
+ 1,
+ 1
+ ],
+ "hierarchy": "."
+ },
+ "fromClient": {
+ "id": 1,
+ "accountNo": "000000001",
+ "status": {
+ "id": 300,
+ "code": "clientStatusType.active",
+ "value": "Active"
+ },
+ "active": true,
+ "activationDate": [
+ 2013,
+ 3,
+ 1
+ ],
+ "fullname": "Small shop",
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO",
+ "groups": []
+ },
+ "fromAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "fromOfficeOptions": [
+ {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO"
+ },
+ {
+ "id": 2,
+ "name": "Branch 1",
+ "nameDecorated": "....Branch 1"
+ }
+ ],
+ "fromClientOptions": [
+ {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ {
+ "id": 3,
+ "displayName": "Third client",
+ "officeId": 1,
+ "officeName": "HO"
+ }
+ ],
+ "fromAccountTypeOptions": [
+ {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ }
+ ],
+ "fromAccountOptions": [
+ {
+ "id": 1,
+ "accountNo": "000000001",
+ "clientId": 1,
+ "clientName": "Small shop",
+ "productId": 1,
+ "productName": "Passbook",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ }
+ },
+ {
+ "id": 3,
+ "accountNo": "000000003",
+ "clientId": 1,
+ "clientName": "Small shop",
+ "productId": 2,
+ "productName": "Shilling product",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "KES",
+ "name": "Kenyan Shilling",
+ "decimalPlaces": 0,
+ "inMultiplesOf": 0,
+ "displaySymbol": "KSh",
+ "nameCode": "currency.KES",
+ "displayLabel": "Kenyan Shilling (KSh)"
+ }
+ }
+ ],
+ "toOfficeOptions": [
+ {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO"
+ },
+ {
+ "id": 2,
+ "name": "Branch 1",
+ "nameDecorated": "....Branch 1"
+ }
+ ],
+ "toAccountTypeOptions": [
+ {
+ "id": 1,
+ "code": "accountType.loan",
+ "value": "Loan Account"
+ },
+ {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ }
+ ]
+}
+
+
+
+
+ GET https://Domain Name/api/v1/accounttransfers/template?fromClientId=1&fromAccountType=2&fromAccountId=1
+
+
+{
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "transferAmount": 0,
+ "transferDate": [
+ 2013,
+ 8,
+ 15
+ ],
+ "fromOffice": {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO",
+ "externalId": "1",
+ "openingDate": [
+ 2009,
+ 1,
+ 1
+ ],
+ "hierarchy": "."
+ },
+ "fromClient": {
+ "id": 1,
+ "accountNo": "000000001",
+ "status": {
+ "id": 300,
+ "code": "clientStatusType.active",
+ "value": "Active"
+ },
+ "active": true,
+ "activationDate": [
+ 2013,
+ 3,
+ 1
+ ],
+ "fullname": "Small shop",
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO",
+ "groups": []
+ },
+ "fromAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "fromAccount": {
+ "id": 1,
+ "accountNo": "000000001",
+ "clientId": 1,
+ "clientName": "Small shop",
+ "productId": 1,
+ "productName": "Passbook",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ }
+ },
+ "fromOfficeOptions": [
+ {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO"
+ },
+ {
+ "id": 2,
+ "name": "Branch 1",
+ "nameDecorated": "....Branch 1"
+ }
+ ],
+ "fromClientOptions": [
+ {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ {
+ "id": 3,
+ "displayName": "Third client",
+ "officeId": 1,
+ "officeName": "HO"
+ }
+ ],
+ "fromAccountTypeOptions": [
+ {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ }
+ ],
+ "fromAccountOptions": [
+ {
+ "id": 1,
+ "accountNo": "000000001",
+ "clientId": 1,
+ "clientName": "Small shop",
+ "productId": 1,
+ "productName": "Passbook",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ }
+ },
+ {
+ "id": 3,
+ "accountNo": "000000003",
+ "clientId": 1,
+ "clientName": "Small shop",
+ "productId": 2,
+ "productName": "Shilling product",
+ "fieldOfficerId": 0,
+ "currency": {
+ "code": "KES",
+ "name": "Kenyan Shilling",
+ "decimalPlaces": 0,
+ "inMultiplesOf": 0,
+ "displaySymbol": "KSh",
+ "nameCode": "currency.KES",
+ "displayLabel": "Kenyan Shilling (KSh)"
+ }
+ }
+ ],
+ "toOfficeOptions": [
+ {
+ "id": 1,
+ "name": "HO",
+ "nameDecorated": "HO"
+ },
+ {
+ "id": 2,
+ "name": "Branch 1",
+ "nameDecorated": "....Branch 1"
+ }
+ ],
+ "toAccountTypeOptions": [
+ {
+ "id": 1,
+ "code": "accountType.loan",
+ "value": "Loan Account"
+ },
+ {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ }
+ ]
+}
+
+
+
+
+
+
+
+
+
Create new Transfer
+
Ability to create new transfer of monetary funds from one account to another.
+
+
+ POST https://Domain Name/api/v1/accounttransfers
+ POST accounttransfers/
+Content-Type: application/json
+No Request Body:
+{
+"fromOfficeId": 1,
+"fromClientId": 1,
+"fromAccountType": 2,
+"fromAccountId": 1,
+"toOfficeId": 1,
+"toClientId": 1,
+"toAccountType": 2,
+"toAccountId": 2,
+"dateFormat": "dd MMMM yyyy",
+"locale": "en",
+"transferDate": "01 August 2011",
+"transferAmount": "112.45",
+"transferDescription": "A description of the transfer"
+}
+
+
+{
+ "savingsId": 1,
+ "resourceId": 1
+}
+
+
+
+
+
+
+
+
List account transfers
+
Example Requests:
+
accounttransfers
+
+
+
+ GET https://Domain Name/api/v1/accounttransfers
+
+{
+ "totalFilteredRecords": 4,
+ "pageItems": [
+ {
+ "id": 1,
+ "reversed": false,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "transferAmount": 200,
+ "transferDate": [
+ 2013,
+ 4,
+ 1
+ ],
+ "transferDescription": "pay off loan from savings.",
+ "fromOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "fromClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "fromAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "fromAccount": {
+ "id": 1,
+ "accountNo": "000000001"
+ },
+ "toOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "toClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "toAccountType": {
+ "id": 1,
+ "code": "accountType.loan",
+ "value": "Loan Account"
+ },
+ "toAccount": {
+ "id": 1,
+ "accountNo": "000000001"
+ }
+ },
+ {
+ "id": 2,
+ "reversed": false,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "transferAmount": 112.45,
+ "transferDate": [
+ 2013,
+ 6,
+ 1
+ ],
+ "transferDescription": "A description of the transfer",
+ "fromOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "fromClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "fromAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "fromAccount": {
+ "id": 1,
+ "accountNo": "000000001"
+ },
+ "toOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "toClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "toAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "toAccount": {
+ "id": 3,
+ "accountNo": "000000003"
+ }
+ },
+ {
+ "id": 3,
+ "reversed": false,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "transferAmount": 112.45,
+ "transferDate": [
+ 2013,
+ 6,
+ 1
+ ],
+ "transferDescription": "A description of the transfer",
+ "fromOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "fromClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "fromAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "fromAccount": {
+ "id": 1,
+ "accountNo": "000000001"
+ },
+ "toOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "toClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "toAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "toAccount": {
+ "id": 3,
+ "accountNo": "000000003"
+ }
+ },
+ {
+ "id": 4,
+ "reversed": false,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "transferAmount": 112.45,
+ "transferDate": [
+ 2013,
+ 6,
+ 1
+ ],
+ "transferDescription": "A description of the transfer",
+ "fromOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "fromClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "fromAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "fromAccount": {
+ "id": 1,
+ "accountNo": "000000001"
+ },
+ "toOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "toClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "toAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "toAccount": {
+ "id": 3,
+ "accountNo": "000000003"
+ }
+ }
+ ]
+}
+
+
+
+
+
+
+
+
Retrieve account transfer:
+
Example Requests :
+
accounttransfers/1
+
+
+
+ GET https://DomainName/api/v1/accounttransfers/{transferId}
+
+{
+ "id": 1,
+ "reversed": false,
+ "currency": {
+ "code": "USD",
+ "name": "US Dollar",
+ "decimalPlaces": 2,
+ "displaySymbol": "$",
+ "nameCode": "currency.USD",
+ "displayLabel": "US Dollar ($)"
+ },
+ "transferAmount": 200,
+ "transferDate": [
+ 2013,
+ 4,
+ 1
+ ],
+ "transferDescription": "pay off loan from savings.",
+ "fromOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "fromClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "fromAccountType": {
+ "id": 2,
+ "code": "accountType.savings",
+ "value": "Savings Account"
+ },
+ "fromAccount": {
+ "id": 1,
+ "accountNo": "000000001"
+ },
+ "toOffice": {
+ "id": 1,
+ "name": "HO"
+ },
+ "toClient": {
+ "id": 1,
+ "displayName": "Small shop",
+ "officeId": 1,
+ "officeName": "HO"
+ },
+ "toAccountType": {
+ "id": 1,
+ "code": "accountType.loan",
+ "value": "Loan Account"
+ },
+ "toAccount": {
+ "id": 1,
+ "accountNo": "000000001"
+ }
+}
+
+
+
+
@@ -17021,6 +18312,14 @@ GET https://DomainName/api/v1/search?query=000000111
|
|
+
+ |
+ clients/{clientId}?command=transfer |
+ Transfer Client |
+ |
+ |
+ |
+
| Client Identifiers |
clients/{clientId}/identifiers |
@@ -17105,6 +18404,15 @@ GET https://DomainName/api/v1/search?query=000000111
|
|
+
+ |
+ groups/{groupId}/accounts |
+ |
+ Retrive Group accounts summary |
+ |
+ |
+
+
| Loan Product |
loanproducts |
@@ -18031,14 +19339,6 @@ GET https://DomainName/api/v1/search?query=000000111
|
|
-
- |
- savingsaccounts/{accountId}/transactions/template |
- |
- Retrieve savings account transaction template |
- |
- |
-
| Savings Transactions |
savingsaccounts/{accountId}/transactions?command=deposit |
@@ -18062,7 +19362,55 @@ GET https://DomainName/api/v1/search?query=000000111
|
|
|
-
+
+
+ |
+ savingsaccounts/{accountId}/transactions/{transactionId}?command=modify |
+ Adjust transaction |
+ |
+ |
+ |
+
+
+ |
+ savingsaccounts/{accountId}/transactions/template |
+ |
+ Retrieve savings account transaction template |
+ |
+ |
+
+
+ |
+ savingsaccounts/{accountId}/transactions/{transactionId} |
+ |
+ Retrieve savings account transaction |
+ |
+ |
+
+
+ | Account Transfers |
+ accounttransfers/ |
+ Create new Transfer |
+ List Transfers |
+ |
+ |
+
+
+ |
+ accounttransfers/template |
+ |
+ Retrieve Template |
+ |
+ |
+
+
+ |
+ accounttransfers/{transferId} |
+ |
+ Retrieve Transfer |
+ |
+ |
+
diff --git a/mifosng-db/migrations/core_db/V100__Group_saving_summary_report.sql b/mifosng-db/migrations/core_db/V100__Group_saving_summary_report.sql
new file mode 100644
index 000000000..d98372bbf
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V100__Group_saving_summary_report.sql
@@ -0,0 +1 @@
+INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('GroupSavingSummary', 'Table', NULL, NULL, 'select ifnull(cur.display_symbol, sa.currency_code) as currency,\ncount(sa.id) as totalSavingAccounts, ifnull(sum(sa.account_balance_derived),0) as totalSavings\nfrom m_group topgroup\njoin m_office o on o.id = topgroup.office_id and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_savings_account sa on sa.group_id = g.id\nleft join m_currency cur on cur.code = sa.currency_code\nwhere topgroup.id = ${groupId}\nand sa.activatedon_date is not null\ngroup by sa.currency_code', 'Utility query for getting group or center saving summary details for a group_id', 1, 0);
\ No newline at end of file
diff --git a/mifosng-db/migrations/core_db/V101__add_mulitplesof_to_account_transfers_table.sql b/mifosng-db/migrations/core_db/V101__add_mulitplesof_to_account_transfers_table.sql
new file mode 100644
index 000000000..1cc31bce7
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V101__add_mulitplesof_to_account_transfers_table.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_savings_account_transfer`
+ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL DEFAULT NULL AFTER `currency_digits`;
\ No newline at end of file
diff --git a/mifosng-db/migrations/core_db/V102__client_attendance_tables.sql b/mifosng-db/migrations/core_db/V102__client_attendance_tables.sql
new file mode 100644
index 000000000..e55b58ac8
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V102__client_attendance_tables.sql
@@ -0,0 +1,30 @@
+CREATE TABLE `m_meeting` (
+ `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+ `calendar_instance_id` BIGINT(20) NOT NULL,
+ `meeting_date` DATE NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `unique_calendar_instance_id_meeting_date` (`calendar_instance_id`, `meeting_date`),
+ CONSTRAINT `FK_m_calendar_instance_m_meeting` FOREIGN KEY (`calendar_instance_id`) REFERENCES `m_calendar_instance` (`id`)
+)
+COLLATE='latin1_swedish_ci'
+ENGINE=InnoDB;
+
+CREATE TABLE `m_client_attendance` (
+ `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+ `client_id` BIGINT(20) NOT NULL DEFAULT '0',
+ `meeting_id` BIGINT(20) NOT NULL,
+ `attendance_type_enum` SMALLINT(5) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `unique_client_meeting_attendance` (`client_id`, `meeting_id`),
+ INDEX `FK_m_meeting_m_client_attendance` (`meeting_id`),
+ CONSTRAINT `FK_m_client_m_client_attendance` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+ CONSTRAINT `FK_m_meeting_m_client_attendance` FOREIGN KEY (`meeting_id`) REFERENCES `m_meeting` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_MEETING', 'MEETING', 'CREATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_MEETING', 'MEETING', 'UPDATE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_MEETING', 'MEETING', 'DELETE', 0);
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'SAVEORUPDATEATTENDANCE_MEETING', 'MEETING', 'SAVEORUPDATEATTENDANCE', 0);
diff --git a/mifosng-db/migrations/core_db/V103__cluster_support_for_batch_jobs.sql b/mifosng-db/migrations/core_db/V103__cluster_support_for_batch_jobs.sql
new file mode 100755
index 000000000..7343f7f74
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V103__cluster_support_for_batch_jobs.sql
@@ -0,0 +1,14 @@
+ALTER TABLE `job`
+ ADD COLUMN `is_misfired` TINYINT(1) NOT NULL DEFAULT '0' AFTER `scheduler_group`;
+
+CREATE TABLE `scheduler_detail` (
+ `id` SMALLINT(2) NOT NULL AUTO_INCREMENT,
+ `is_suspended` TINYINT(1) NOT NULL DEFAULT '0',
+ `execute_misfired_jobs` TINYINT(1) NOT NULL DEFAULT '1',
+ `reset_scheduler_on_bootup` TINYINT(1) NOT NULL DEFAULT '1',
+ PRIMARY KEY (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB;
+
+INSERT INTO `scheduler_detail` (`is_suspended`, `execute_misfired_jobs`, `reset_scheduler_on_bootup`) VALUES (FALSE,TRUE,TRUE);
diff --git a/mifosng-db/migrations/core_db/V104__permissions_for_transfers.sql b/mifosng-db/migrations/core_db/V104__permissions_for_transfers.sql
new file mode 100644
index 000000000..485010005
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V104__permissions_for_transfers.sql
@@ -0,0 +1,11 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio_group', 'TRANSFERCLIENTS_GROUP', 'GROUP', 'TRANSFERCLIENTS', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio_group', 'TRANSFERCLIENTS_GROUP_CHECKER', 'GROUP', 'TRANSFERCLIENTS', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'TRANSFER_CLIENT', 'CLIENT', 'TRANSFER', 0);
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('portfolio', 'TRANSFER_CLIENT_CHECKER', 'CLIENT', 'TRANSFER', 0);
\ No newline at end of file
diff --git a/mifosng-db/migrations/core_db/V92__configuration_settings_for_holiday_and_non_workingday.sql b/mifosng-db/migrations/core_db/V91_1__configuration_settings_for_holiday_and_non_workingday.sql
similarity index 100%
rename from mifosng-db/migrations/core_db/V92__configuration_settings_for_holiday_and_non_workingday.sql
rename to mifosng-db/migrations/core_db/V91_1__configuration_settings_for_holiday_and_non_workingday.sql
diff --git a/mifosng-db/migrations/core_db/V92__group_center_assign_staff_permission.sql b/mifosng-db/migrations/core_db/V92__group_center_assign_staff_permission.sql
new file mode 100644
index 000000000..d47106776
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V92__group_center_assign_staff_permission.sql
@@ -0,0 +1,2 @@
+INSERT INTO `m_permission` (grouping,code,`entity_name`,`action_name`,`can_maker_checker`)
+values('portfolio_group','ASSIGNSTAFF_GROUP','GROUP','ASSIGNSTAFF',0);
\ No newline at end of file
diff --git a/mifosng-db/migrations/core_db/V93__loan_transaction_external_id.sql b/mifosng-db/migrations/core_db/V93__loan_transaction_external_id.sql
new file mode 100644
index 000000000..1e5cfdfc6
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V93__loan_transaction_external_id.sql
@@ -0,0 +1,3 @@
+ALTER TABLE m_loan_transaction
+ADD COLUMN `external_id` VARCHAR(100) NULL DEFAULT NULL AFTER `is_reversed` ,
+ADD UNIQUE INDEX `external_id_UNIQUE` (`external_id` ASC) ;
\ No newline at end of file
diff --git a/mifosng-db/migrations/core_db/V94__added_savings_accont type.sql b/mifosng-db/migrations/core_db/V94__added_savings_accont type.sql
new file mode 100755
index 000000000..87985e710
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V94__added_savings_accont type.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `m_savings_account`
+ ADD COLUMN `account_type_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `status_enum`;
\ No newline at end of file
diff --git a/mifosng-db/migrations/core_db/V95__batch_job_postInterest.sql b/mifosng-db/migrations/core_db/V95__batch_job_postInterest.sql
new file mode 100644
index 000000000..58fda9d28
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V95__batch_job_postInterest.sql
@@ -0,0 +1 @@
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`) VALUES ('Post Interest For Savings', 'Post Interest For Savings', '0 0 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0);
diff --git a/mifosng-db/migrations/core_db/V96__savings_accounts_transfers_table.sql b/mifosng-db/migrations/core_db/V96__savings_accounts_transfers_table.sql
new file mode 100644
index 000000000..425b17b4a
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V96__savings_accounts_transfers_table.sql
@@ -0,0 +1,56 @@
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
+VALUES
+('transaction_savings', 'READ_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'READ', 0),
+('transaction_savings', 'CREATE_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'CREATE', 1),
+('transaction_savings', 'CREATE_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'CREATE', 0);
+
+
+DROP TABLE IF EXISTS `m_savings_account_transfer`;
+
+CREATE TABLE `m_savings_account_transfer` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `from_office_id` bigint(20) NOT NULL,
+ `to_office_id` bigint(20) NOT NULL,
+ `from_client_id` bigint(20) NOT NULL,
+ `to_client_id` bigint(20) NOT NULL,
+ `from_savings_account_id` bigint(20) DEFAULT NULL,
+ `to_savings_account_id` bigint(20) DEFAULT NULL,
+ `from_loan_account_id` bigint(20) DEFAULT NULL,
+ `to_loan_account_id` bigint(20) DEFAULT NULL,
+ `from_savings_transaction_id` bigint(20) DEFAULT NULL,
+ `from_loan_transaction_id` bigint(20) DEFAULT NULL,
+ `to_savings_transaction_id` bigint(20) DEFAULT NULL,
+ `to_loan_transaction_id` bigint(20) DEFAULT NULL,
+ `is_reversed` tinyint(1) NOT NULL,
+ `transaction_date` date NOT NULL,
+ `currency_code` varchar(3) NOT NULL,
+ `currency_digits` smallint(5) NOT NULL,
+ `amount` decimal(19,6) NOT NULL,
+ `description` varchar(200) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `FKTRAN000000001` (`from_office_id`),
+ KEY `FKTRAN000000002` (`from_client_id`),
+ KEY `FKTRAN000000003` (`from_savings_account_id`),
+ KEY `FKTRAN000000004` (`to_office_id`),
+ KEY `FKTRAN000000005` (`to_client_id`),
+ KEY `FKTRAN000000006` (`to_savings_account_id`),
+ KEY `FKTRAN000000007` (`to_loan_account_id`),
+ KEY `FKTRAN000000008` (`from_savings_transaction_id`),
+ KEY `FKTRAN000000009` (`to_savings_transaction_id`),
+ KEY `FKTRAN000000010` (`to_loan_transaction_id`),
+ KEY `FKTRAN000000011` (`from_loan_account_id`),
+ KEY `FKTRAN000000012` (`from_loan_transaction_id`),
+ CONSTRAINT `FKTRAN000000001` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
+ CONSTRAINT `FKTRAN000000002` FOREIGN KEY (`from_client_id`) REFERENCES `m_client` (`id`),
+ CONSTRAINT `FKTRAN000000003` FOREIGN KEY (`from_savings_account_id`) REFERENCES `m_savings_account` (`id`),
+ CONSTRAINT `FKTRAN000000004` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`),
+ CONSTRAINT `FKTRAN000000005` FOREIGN KEY (`to_client_id`) REFERENCES `m_client` (`id`),
+ CONSTRAINT `FKTRAN000000006` FOREIGN KEY (`to_savings_account_id`) REFERENCES `m_savings_account` (`id`),
+ CONSTRAINT `FKTRAN000000007` FOREIGN KEY (`to_loan_account_id`) REFERENCES `m_loan` (`id`),
+ CONSTRAINT `FKTRAN000000008` FOREIGN KEY (`from_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
+ CONSTRAINT `FKTRAN000000009` FOREIGN KEY (`to_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
+ CONSTRAINT `FKTRAN000000010` FOREIGN KEY (`to_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
+ CONSTRAINT `FKTRAN000000011` FOREIGN KEY (`from_loan_account_id`) REFERENCES `m_loan` (`id`),
+ CONSTRAINT `FKTRAN000000012` FOREIGN KEY (`from_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
\ No newline at end of file
diff --git a/mifosng-db/migrations/core_db/V97__add_permission_for_adjust_savings_transaction.sql b/mifosng-db/migrations/core_db/V97__add_permission_for_adjust_savings_transaction.sql
new file mode 100644
index 000000000..906bb1497
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V97__add_permission_for_adjust_savings_transaction.sql
@@ -0,0 +1 @@
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_savings', 'ADJUSTTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ADJUSTTRANSACTION', 0);
\ No newline at end of file
diff --git a/mifosng-db/migrations/core_db/V98__added_currency_roundof_for_multipleof.sql b/mifosng-db/migrations/core_db/V98__added_currency_roundof_for_multipleof.sql
new file mode 100644
index 000000000..571335a04
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V98__added_currency_roundof_for_multipleof.sql
@@ -0,0 +1,12 @@
+ALTER TABLE `m_savings_account`
+ ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
+ALTER TABLE `m_savings_product`
+ ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
+ALTER TABLE `m_loan`
+ ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
+ALTER TABLE `m_product_loan`
+ ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
+ALTER TABLE `m_currency`
+ ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `decimal_places`;
+ALTER TABLE `m_organisation_currency`
+ ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `decimal_places`;
\ No newline at end of file
diff --git a/mifosng-provider/gradle.properties b/mifosng-provider/gradle.properties
index ba394435c..2a3082a2b 100644
--- a/mifosng-provider/gradle.properties
+++ b/mifosng-provider/gradle.properties
@@ -1 +1 @@
-releaseVersion=1.7.2.RELEASE
+releaseVersion=1.8.0.RELEASE
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/GroupLoanIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/GroupLoanIntegrationTest.java
new file mode 100644
index 000000000..b309d9852
--- /dev/null
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/GroupLoanIntegrationTest.java
@@ -0,0 +1,117 @@
+package org.mifosplatform.integrationtests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mifosplatform.integrationtests.common.ClientHelper;
+import org.mifosplatform.integrationtests.common.GroupHelper;
+import org.mifosplatform.integrationtests.common.Utils;
+import org.mifosplatform.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
+import org.mifosplatform.integrationtests.common.loans.LoanTransactionHelper;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Group Loan Integration Test for checking Loan Application Repayment
+ * Schedule.
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class GroupLoanIntegrationTest {
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+ private LoanTransactionHelper loanTransactionHelper;
+
+ @Before
+ public void setup() {
+ Utils.initializeRESTAssured();
+ requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+ }
+
+ @Test
+ public void checkGroupLoanCreateAndDisburseFlow() {
+ loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
+
+ Integer clientID = ClientHelper.createClient(requestSpec, responseSpec);
+ Integer groupID = GroupHelper.createGroup(requestSpec, responseSpec, true);
+ groupID = GroupHelper.associateClient(requestSpec, responseSpec, groupID.toString(),clientID.toString());
+
+ Integer loanProductID = createLoanProduct();
+ Integer loanID = applyForLoanApplication(groupID, loanProductID);
+ ArrayList loanSchedule = loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
+ verifyLoanRepaymentSchedule(loanSchedule);
+
+ }
+
+ private Integer createLoanProduct() {
+ System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+ String loanProductJSON = new LoanProductTestBuilder() //
+ .withPrincipal("12,000.00") //
+ .withNumberOfRepayments("4") //
+ .withRepaymentAfterEvery("1") //
+ .withRepaymentTypeAsMonth() //
+ .withinterestRatePerPeriod("1") //
+ .withInterestRateFrequencyTypeAsMonths() //
+ .withAmortizationTypeAsEqualInstallments() //
+ .withInterestTypeAsDecliningBalance() //
+ .build();
+ return loanTransactionHelper.getLoanProductId(loanProductJSON);
+ }
+
+ private Integer applyForLoanApplication(final Integer groupID, final Integer loanProductID) {
+ System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+ String loanApplicationJSON = new LoanApplicationTestBuilder() //
+ .withPrincipal("12,000.00") //
+ .withLoanTermFrequency("4") //
+ .withLoanTermFrequencyAsMonths() //
+ .withNumberOfRepayments("4") //
+ .withRepaymentEveryAfter("1") //
+ .withRepaymentFrequencyTypeAsMonths() //
+ .withInterestRatePerPeriod("2") //
+ .withAmortizationTypeAsEqualInstallments() //
+ .withInterestTypeAsDecliningBalance() //
+ .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+ .withExpectedDisbursementDate("20 September 2011") //
+ .withSubmittedOnDate("20 September 2011") //
+ .withLoanType("group")
+ .build(groupID.toString(), loanProductID.toString());
+ System.out.println(loanApplicationJSON);
+ return loanTransactionHelper.getLoanId(loanApplicationJSON);
+ }
+
+ private void verifyLoanRepaymentSchedule(final ArrayList loanSchedule) {
+ System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
+
+ assertEquals("Checking for Due Date for 1st Month", new ArrayList(Arrays.asList(2011, 10, 20)),
+ loanSchedule.get(1).get("dueDate"));
+ assertEquals("Checking for Principal Due for 1st Month", new Float("2911.49"), loanSchedule.get(1).get("principalOriginalDue"));
+ assertEquals("Checking for Interest Due for 1st Month", new Float("240.00"), loanSchedule.get(1).get("interestOriginalDue"));
+
+ assertEquals("Checking for Due Date for 2nd Month", new ArrayList(Arrays.asList(2011, 11, 20)),
+ loanSchedule.get(2).get("dueDate"));
+ assertEquals("Checking for Principal Due for 2nd Month", new Float("2969.71"), loanSchedule.get(2).get("principalDue"));
+ assertEquals("Checking for Interest Due for 2nd Month", new Float("181.77"), loanSchedule.get(2).get("interestOriginalDue"));
+
+ assertEquals("Checking for Due Date for 3rd Month", new ArrayList(Arrays.asList(2011, 12, 20)),
+ loanSchedule.get(3).get("dueDate"));
+ assertEquals("Checking for Principal Due for 3rd Month", new Float("3029.1"), loanSchedule.get(3).get("principalDue"));
+ assertEquals("Checking for Interest Due for 3rd Month", new Float("122.38"), loanSchedule.get(3).get("interestOriginalDue"));
+
+ assertEquals("Checking for Due Date for 4th Month", new ArrayList(Arrays.asList(2012, 1, 20)),
+ loanSchedule.get(4).get("dueDate"));
+ assertEquals("Checking for Principal Due for 4th Month", new Float("3089.7"), loanSchedule.get(4).get("principalDue"));
+ assertEquals("Checking for Interest Due for 4th Month", new Float("61.79"), loanSchedule.get(4).get("interestOriginalDue"));
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/GroupTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/GroupTest.java
new file mode 100644
index 000000000..4b8374474
--- /dev/null
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/GroupTest.java
@@ -0,0 +1,55 @@
+package org.mifosplatform.integrationtests;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mifosplatform.integrationtests.common.ClientHelper;
+import org.mifosplatform.integrationtests.common.GroupHelper;
+import org.mifosplatform.integrationtests.common.Utils;
+
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.builder.ResponseSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+/**
+ * Group Test for checking Group: Creation, Activation, Client Association,
+ * Updating & Deletion
+ */
+public class GroupTest {
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+
+ @Before
+ public void setup() {
+ Utils.initializeRESTAssured();
+ requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+ }
+
+ @Test
+ public void checkGroupFunctions() {
+ Integer clientID = ClientHelper.createClient(requestSpec, responseSpec);
+ Integer groupID = GroupHelper.createGroup(requestSpec, responseSpec);
+ GroupHelper.verifyGroupCreatedOnServer(requestSpec, responseSpec, groupID);
+
+ groupID = GroupHelper.activateGroup(requestSpec, responseSpec, groupID.toString());
+ GroupHelper.verifyGroupActivatedOnServer(requestSpec, responseSpec, groupID, true);
+
+ groupID = GroupHelper.associateClient(requestSpec, responseSpec, groupID.toString(), clientID.toString());
+ GroupHelper.verifyGroupMembers(requestSpec, responseSpec, groupID, clientID);
+
+ groupID = GroupHelper.disAssociateClient(requestSpec, responseSpec, groupID.toString(), clientID.toString());
+ GroupHelper.verifyEmptyGroupMembers(requestSpec, responseSpec, groupID);
+
+ String updatedGroupName = GroupHelper.randomNameGenerator("Group-", 5);
+ groupID = GroupHelper.updateGroup(requestSpec, responseSpec, updatedGroupName, groupID.toString());
+ GroupHelper.verifyGroupDetails(requestSpec, responseSpec, groupID, "name", updatedGroupName);
+
+ groupID = GroupHelper.createGroup(requestSpec, responseSpec);
+ GroupHelper.deleteGroup(requestSpec, responseSpec, groupID.toString());
+ GroupHelper.verifyGroupDeleted(requestSpec, responseSpec, groupID);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/GroupHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/GroupHelper.java
new file mode 100644
index 000000000..973da206c
--- /dev/null
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/GroupHelper.java
@@ -0,0 +1,174 @@
+package org.mifosplatform.integrationtests.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import com.google.gson.Gson;
+import com.jayway.restassured.specification.RequestSpecification;
+import com.jayway.restassured.specification.ResponseSpecification;
+
+public class GroupHelper {
+
+ private static final String CREATE_GROUP_URL = "/mifosng-provider/api/v1/groups?tenantIdentifier=default";
+
+ public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ @SuppressWarnings("unused") final boolean active) {
+ System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
+ return createGroup(requestSpec, responseSpec, "04 March 2011");
+ }
+
+ public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final String activationDate) {
+ System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
+ return Utils.performServerPost(requestSpec, responseSpec, CREATE_GROUP_URL, getTestGroupAsJSON(true, activationDate), "groupId");
+ }
+
+ public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+ System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
+ return Utils.performServerPost(requestSpec, responseSpec, CREATE_GROUP_URL, getTestGroupAsJSON(false, ""), "groupId");
+ }
+
+ public static Integer associateClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final String groupId, final String clientMember) {
+ String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?command=associateClients&tenantIdentifier=default";
+ System.out.println("---------------------------------Associate Client To A GROUP---------------------------------------------");
+ return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, associateClientAsJSON(clientMember), "groupId");
+ }
+
+ public static Integer disAssociateClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final String groupId, final String clientMember) {
+ String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?command=disassociateClients&tenantIdentifier=default";
+ System.out.println("---------------------------------Disassociate Client To A GROUP---------------------------------------------");
+ return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, associateClientAsJSON(clientMember), "groupId");
+ }
+
+ public static Integer activateGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final String groupId) {
+ String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?command=activate&tenantIdentifier=default";
+ System.out.println("---------------------------------Activate A GROUP---------------------------------------------");
+ return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, activateGroupAsJSON(""), "groupId");
+ }
+
+ public static Integer updateGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String name,
+ final String groupId) {
+ String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?tenantIdentifier=default";
+ System.out.println("---------------------------------UPDATE GROUP---------------------------------------------");
+ return Utils.performServerPut(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, updateGroupAsJSON(name), "groupId");
+ }
+
+ public static Integer deleteGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String groupId) {
+ String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?tenantIdentifier=default";
+ System.out.println("---------------------------------DELETE GROUP---------------------------------------------");
+ return Utils.performServerDelete(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, "groupId");
+ }
+
+ public static String getTestGroupAsJSON(final boolean active, final String activationDate) {
+ HashMap map = new HashMap();
+ map.put("officeId", "1");
+ map.put("name", randomNameGenerator("Group_Name_", 5));
+ map.put("externalId", randomIDGenerator("ID_", 7));
+ if (active == true) {
+ map.put("active", "true");
+ map.put("dateFormat", "dd MMMM yyyy");
+ map.put("locale", "en");
+ map.put("activationDate", activationDate);
+ } else
+ map.put("active", "false");
+
+ System.out.println("map : " + map);
+ return new Gson().toJson(map);
+ }
+
+ public static String associateClientAsJSON(final String clientMember) {
+ HashMap> map = new HashMap>();
+ List list = new ArrayList();
+ list.add(clientMember);
+ map.put("clientMembers", list);
+ System.out.println("map : " + map);
+ return new Gson().toJson(map);
+ }
+
+ public static String activateGroupAsJSON(final String activationDate) {
+ HashMap map = new HashMap();
+ map.put("dateFormat", "dd MMMM yyyy");
+ map.put("locale", "en");
+ if (activationDate != "")
+ map.put("activationDate", activationDate);
+ else
+ map.put("activationDate", "04 March 2011");
+ System.out.println("map : " + map);
+ return new Gson().toJson(map);
+ }
+
+ public static String updateGroupAsJSON(final String name) {
+ HashMap map = new HashMap();
+ map.put("name", name);
+ System.out.println("map : " + map);
+ return new Gson().toJson(map);
+ }
+
+ public static void verifyGroupCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final Integer generatedGroupID) {
+ System.out.println("------------------------------CHECK GROUP DETAILS------------------------------------\n");
+ String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
+ Integer responseGroupID = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "id");
+ assertEquals("ERROR IN CREATING THE GROUP", generatedGroupID, responseGroupID);
+ }
+
+ public static void verifyGroupDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final Integer generatedGroupID, final String field, final String expectedValue) {
+ System.out.println("------------------------------CHECK GROUP DETAILS------------------------------------\n");
+ String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
+ String responseValue = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, field);
+ assertEquals("ERROR IN CREATING THE GROUP", expectedValue, responseValue);
+ }
+
+ public static void verifyGroupActivatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final Integer generatedGroupID, final boolean generatedGroupStatus) {
+ System.out.println("------------------------------CHECK GROUP STATUS------------------------------------\n");
+ String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
+ Boolean responseGroupStatus = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "active");
+ assertEquals("ERROR IN ACTIVATING THE GROUP", generatedGroupStatus, responseGroupStatus);
+ }
+
+ public static void verifyGroupMembers(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final Integer generatedGroupID, final Integer groupMember) {
+ List list = new ArrayList();
+ System.out.println("------------------------------CHECK GROUP MEMBERS------------------------------------\n");
+ String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?associations=clientMembers&tenantIdentifier=default";
+ list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "clientMembers");
+ assertTrue("ERROR IN GROUP MEMBER", list.toString().contains("id=" + groupMember.toString()));
+ }
+
+ public static void verifyEmptyGroupMembers(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final Integer generatedGroupID) {
+ List list = new ArrayList();
+ System.out.println("------------------------------CHECK EMPTY GROUP MEMBER LIST------------------------------------\n");
+ String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?associations=clientMembers&tenantIdentifier=default";
+ list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "clientMembers");
+ assertEquals("GROUP MEMBER LIST NOT EMPTY", list, null);
+ }
+
+ public static void verifyGroupDeleted(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final Integer generatedGroupID) {
+ List list = new ArrayList();
+ System.out.println("------------------------------CHECK GROUP DELETED------------------------------------\n");
+ String GROUP_URL = "/mifosng-provider/api/v1/groups/?tenantIdentifier=default";
+ list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "pageItems");
+
+ assertFalse("GROUP NOT DELETED", list.toString().contains("id=" + generatedGroupID.toString()));
+ }
+
+ public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
+ return Utils.randomStringGenerator(prefix, lenOfRandomSuffix);
+ }
+
+ private static String randomIDGenerator(final String prefix, final int lenOfRandomSuffix) {
+ return Utils.randomStringGenerator(prefix, lenOfRandomSuffix, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/Utils.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/Utils.java
index 9cffbaf9c..747fb7045 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/Utils.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/Utils.java
@@ -6,8 +6,6 @@ import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
-import java.util.Random;
-
import org.apache.commons.lang.StringUtils;
import org.apache.http.conn.HttpHostConnectException;
@@ -16,13 +14,19 @@ import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
+import java.lang.System;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Random;
+
@SuppressWarnings("unchecked")
public class Utils {
private static final String LOGIN_URL = "/mifosng-provider/api/v1/authentication?username=mifos&password=password&tenantIdentifier=default";
+
public static void initializeRESTAssured() {
- RestAssured.baseURI = "https://localhost";
+ RestAssured.baseURI ="https://localhost";
RestAssured.port = 8443;
RestAssured.keystore("../keystore.jks", "openmf");
}
@@ -33,10 +37,11 @@ public class Utils {
String json = RestAssured.post(LOGIN_URL).asString();
assertThat("Failed to login into mifosx platform", StringUtils.isBlank(json), is(false));
return JsonPath.with(json).get("base64EncodedAuthenticationKey");
- } catch (Exception e) {
+ }
+ catch (Exception e) {
if (e instanceof HttpHostConnectException) {
HttpHostConnectException hh = (HttpHostConnectException) e;
- fail("Failed to connect to mifosx platform:" + hh.getMessage());
+ fail("Failed to connect to mifosx platform:"+hh.getMessage());
}
throw new RuntimeException(e);
@@ -44,18 +49,52 @@ public class Utils {
}
public static T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
- final String getURL, final String jsonAttributeToGetBack) {
- String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
+ final String getURL, final String jsonAttributeToGetBack) {
+ String json = given().spec(requestSpec)
+ .expect().spec(responseSpec).log().ifError()
+ .when().get(getURL).andReturn().asString();
return (T) from(json).get(jsonAttributeToGetBack);
}
public static T performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
- final String postURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
- String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().post(postURL)
+ final String postURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
+ String json = given().spec(requestSpec).body(jsonBodyToSend)
+ .expect().spec(responseSpec).log().ifError()
+ .when().post(postURL)
.andReturn().asString();
return (T) from(json).get(jsonAttributeToGetBack);
}
+ public static T performServerPut(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final String putURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
+ String json = given().spec(requestSpec).body(jsonBodyToSend)
+ .expect().spec(responseSpec).log().ifError()
+ .when().put(putURL)
+ .andReturn().asString();
+ return (T) from(json).get(jsonAttributeToGetBack);
+ }
+
+ public static T performServerDelete(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+ final String deleteURL, final String jsonAttributeToGetBack) {
+ String json = given().spec(requestSpec)
+ .expect().spec(responseSpec).log().ifError()
+ .when().delete(deleteURL)
+ .andReturn().asString();
+ return (T) from(json).get(jsonAttributeToGetBack);
+ }
+
+ public static String convertDateToURLFormat(String dateToBeConvert){
+ SimpleDateFormat oldFormat = new SimpleDateFormat("dd MMMMMM yyyy");
+ SimpleDateFormat newFormat = new SimpleDateFormat("yyyy-MM-dd");
+ String reformattedStr="";
+ try {
+ reformattedStr = newFormat.format(oldFormat.parse(dateToBeConvert));
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return reformattedStr;
+ }
+
public static String randomStringGenerator(final String prefix, final int len, final String sourceSetString) {
int lengthOfSource = sourceSetString.length();
Random rnd = new Random();
diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java
index b1b0de92a..f9bd1894b 100644
--- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java
+++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java
@@ -33,12 +33,15 @@ public class LoanApplicationTestBuilder {
private String submittedOnDate = "";
private String loanType = "individual";
- public String build(final String clientID, final String loanProductId) {
+ public String build(final String ID, final String loanProductId) {
HashMap map = new HashMap();
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en_GB");
- map.put("clientId", clientID);
+ if (loanType == "group")
+ map.put("groupId", ID);
+ else
+ map.put("clientId", ID);
map.put("productId", loanProductId);
map.put("principal", principal);
map.put("loanTermFrequency", loanTermFrequency);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/closure/domain/GLClosure.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/closure/domain/GLClosure.java
index 5d57e6024..4c141b170 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/closure/domain/GLClosure.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/closure/domain/GLClosure.java
@@ -33,7 +33,6 @@ public class GLClosure extends AbstractAuditableCustom {
@JoinColumn(name = "office_id", nullable = false)
private Office office;
- @SuppressWarnings("unused")
@Column(name = "is_deleted", nullable = false)
private boolean deleted = true;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/domain/JournalEntry.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/domain/JournalEntry.java
index e5298504c..294fea652 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/domain/JournalEntry.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/domain/JournalEntry.java
@@ -35,20 +35,16 @@ public class JournalEntry extends AbstractAuditableCustom {
@JoinColumn(name = "account_id", nullable = false)
private GLAccount glAccount;
- @SuppressWarnings("unused")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reversal_id")
private JournalEntry reversalJournalEntry;
- @SuppressWarnings("unused")
@Column(name = "transaction_id", nullable = false, length = 50)
private String transactionId;
- @SuppressWarnings("unused")
@Column(name = "reversed", nullable = false)
private boolean reversed = false;
- @SuppressWarnings("unused")
@Column(name = "manual_entry", nullable = false)
private boolean manualEntry = false;
@@ -62,15 +58,12 @@ public class JournalEntry extends AbstractAuditableCustom {
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
- @SuppressWarnings("unused")
@Column(name = "description", length = 500)
private String description;
- @SuppressWarnings("unused")
@Column(name = "entity_type_enum", length = 50)
private Integer entityType;
- @SuppressWarnings("unused")
@Column(name = "entity_id")
private Long entityId;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/rule/domain/AccountingTagRule.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/rule/domain/AccountingTagRule.java
index 16b03e806..f43a99cef 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/rule/domain/AccountingTagRule.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/rule/domain/AccountingTagRule.java
@@ -20,7 +20,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "acc_rule_tags", uniqueConstraints = { @UniqueConstraint(columnNames = { "acc_rule_id", "tag_id", "acc_type_enum" }, name = "UNIQUE_ACCOUNT_RULE_TAGS") })
public class AccountingTagRule extends AbstractPersistable {
- @SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "acc_rule_id", nullable = false)
private AccountingRule accountingRule;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java
index b8c948061..3aa5ab685 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java
@@ -25,171 +25,175 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_portfolio_command_source")
public class CommandSource extends AbstractPersistable {
- @Column(name = "action_name", nullable = true, length = 100)
- private String actionName;
+ @Column(name = "action_name", nullable = true, length = 100)
+ private String actionName;
- @Column(name = "entity_name", nullable = true, length = 100)
- private String entityName;
+ @Column(name = "entity_name", nullable = true, length = 100)
+ private String entityName;
- @SuppressWarnings("unused")
- @Column(name = "office_id")
- private Long officeId;
+ @Column(name = "office_id")
+ private Long officeId;
- @SuppressWarnings("unused")
- @Column(name = "group_id")
- private Long groupId;
+ @Column(name = "group_id")
+ private Long groupId;
- @SuppressWarnings("unused")
- @Column(name = "client_id")
- private Long clientId;
+ @Column(name = "client_id")
+ private Long clientId;
- @SuppressWarnings("unused")
- @Column(name = "loan_id")
- private Long loanId;
+ @Column(name = "loan_id")
+ private Long loanId;
- @SuppressWarnings("unused")
- @Column(name = "savings_account_id")
- private Long savingsId;
+ @Column(name = "savings_account_id")
+ private Long savingsId;
- @Column(name = "api_get_url", length = 100)
- private String resourceGetUrl;
+ @Column(name = "api_get_url", length = 100)
+ private String resourceGetUrl;
- @Column(name = "resource_id")
- private Long resourceId;
+ @Column(name = "resource_id")
+ private Long resourceId;
- @Column(name = "subresource_id")
- private Long subresourceId;
+ @Column(name = "subresource_id")
+ private Long subresourceId;
- @Column(name = "command_as_json", length = 1000)
- private String commandAsJson;
+ @Column(name = "command_as_json", length = 1000)
+ private String commandAsJson;
- @SuppressWarnings("unused")
- @ManyToOne
- @JoinColumn(name = "maker_id", nullable = false)
- private AppUser maker;
+ @ManyToOne
+ @JoinColumn(name = "maker_id", nullable = false)
+ private AppUser maker;
- @SuppressWarnings("unused")
- @Column(name = "made_on_date", nullable = false)
- @Temporal(TemporalType.TIMESTAMP)
- private Date madeOnDate;
+ @Column(name = "made_on_date", nullable = false)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date madeOnDate;
- @SuppressWarnings("unused")
- @ManyToOne
- @JoinColumn(name = "checker_id", nullable = true)
- private AppUser checker;
+ @ManyToOne
+ @JoinColumn(name = "checker_id", nullable = true)
+ private AppUser checker;
- @SuppressWarnings("unused")
- @Column(name = "checked_on_date", nullable = true)
- @Temporal(TemporalType.TIMESTAMP)
- private Date checkedOnDate;
+ @Column(name = "checked_on_date", nullable = true)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date checkedOnDate;
- @Column(name = "processing_result_enum", nullable = false)
- private Integer processingResult;
-
- @Column(name = "product_id")
- private Long productId;
+ @Column(name = "processing_result_enum", nullable = false)
+ private Integer processingResult;
- public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) {
- return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(),
- command.json(), maker, DateTime.now());
- }
+ @Column(name = "product_id")
+ private Long productId;
- protected CommandSource() {
- //
- }
+ public static CommandSource fullEntryFrom(final CommandWrapper wrapper,
+ final JsonCommand command, final AppUser maker) {
+ return new CommandSource(wrapper.actionName(), wrapper.entityName(),
+ wrapper.getHref(), command.entityId(), command.subentityId(),
+ command.json(), maker, DateTime.now());
+ }
- private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId,
- final Long subresourceId, final String commandSerializedAsJson, final AppUser maker, final DateTime madeOnDateTime) {
- this.actionName = actionName;
- this.entityName = entityName;
- this.resourceGetUrl = href;
- this.resourceId = resourceId;
- this.subresourceId = subresourceId;
- this.commandAsJson = commandSerializedAsJson;
- this.maker = maker;
- this.madeOnDate = madeOnDateTime.toDate();
- this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
- }
+ protected CommandSource() {
+ //
+ }
- public void markAsChecked(final AppUser checker, final DateTime checkedOnDate) {
- this.checker = checker;
- this.checkedOnDate = checkedOnDate.toDate();
- this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
- }
+ private CommandSource(final String actionName, final String entityName,
+ final String href, final Long resourceId, final Long subresourceId,
+ final String commandSerializedAsJson, final AppUser maker,
+ final DateTime madeOnDateTime) {
+ this.actionName = actionName;
+ this.entityName = entityName;
+ this.resourceGetUrl = href;
+ this.resourceId = resourceId;
+ this.subresourceId = subresourceId;
+ this.commandAsJson = commandSerializedAsJson;
+ this.maker = maker;
+ this.madeOnDate = madeOnDateTime.toDate();
+ this.processingResult = CommandProcessingResultType.PROCESSED
+ .getValue();
+ }
- public void updateResourceId(final Long resourceId) {
- this.resourceId = resourceId;
- }
+ public void markAsChecked(final AppUser checker,
+ final DateTime checkedOnDate) {
+ this.checker = checker;
+ this.checkedOnDate = checkedOnDate.toDate();
+ this.processingResult = CommandProcessingResultType.PROCESSED
+ .getValue();
+ }
- public void updateSubresourceId(final Long subresourceId) {
- this.subresourceId = subresourceId;
- }
+ public void updateResourceId(final Long resourceId) {
+ this.resourceId = resourceId;
+ }
- public void updateJsonTo(final String json) {
- this.commandAsJson = json;
- }
+ public void updateSubresourceId(final Long subresourceId) {
+ this.subresourceId = subresourceId;
+ }
- public Long resourceId() {
- return this.resourceId;
- }
+ public void updateJsonTo(final String json) {
+ this.commandAsJson = json;
+ }
- public Long subresourceId() {
- return this.subresourceId;
- }
+ public Long resourceId() {
+ return this.resourceId;
+ }
- public boolean hasJson() {
- return StringUtils.isNotBlank(this.commandAsJson);
- }
+ public Long subresourceId() {
+ return this.subresourceId;
+ }
- public String json() {
- return this.commandAsJson;
- }
+ public boolean hasJson() {
+ return StringUtils.isNotBlank(this.commandAsJson);
+ }
- public String getActionName() {
- return this.actionName;
- }
+ public String json() {
+ return this.commandAsJson;
+ }
- public String getEntityName() {
- return this.entityName;
- }
+ public String getActionName() {
+ return this.actionName;
+ }
- public String getPermissionCode() {
- return this.actionName + "_" + this.entityName;
- }
+ public String getEntityName() {
+ return this.entityName;
+ }
- public Long getResourceId() {
- return this.resourceId;
- }
+ public String getPermissionCode() {
+ return this.actionName + "_" + this.entityName;
+ }
- public Long getSubresourceId() {
- return this.subresourceId;
- }
+ public Long getResourceId() {
+ return this.resourceId;
+ }
- public void markAsAwaitingApproval() {
- this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
- }
+ public Long getSubresourceId() {
+ return this.subresourceId;
+ }
- public boolean isMarkedAsAwaitingApproval() {
- if (this.processingResult.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue())) return true;
+ public void markAsAwaitingApproval() {
+ this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL
+ .getValue();
+ }
- return false;
- }
+ public boolean isMarkedAsAwaitingApproval() {
+ if (this.processingResult
+ .equals(CommandProcessingResultType.AWAITING_APPROVAL
+ .getValue())) {
+ return true;
+ }
- public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId,
- final Long productId) {
- this.officeId = officeId;
- this.groupId = groupId;
- this.clientId = clientId;
- this.loanId = loanId;
- this.savingsId = savingsId;
- this.productId = productId;
- }
+ return false;
+ }
- public String getResourceGetUrl() {
- return this.resourceGetUrl;
- }
+ public void updateForAudit(final Long officeId, final Long groupId,
+ final Long clientId, final Long loanId, final Long savingsId,
+ final Long productId) {
+ this.officeId = officeId;
+ this.groupId = groupId;
+ this.clientId = clientId;
+ this.loanId = loanId;
+ this.savingsId = savingsId;
+ this.productId = productId;
+ }
- public Long getProductId() {
- return this.productId;
- }
+ public String getResourceGetUrl() {
+ return this.resourceGetUrl;
+ }
+
+ public Long getProductId() {
+ return this.productId;
+ }
}
\ No newline at end of file
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 483bc88ff..90ff1c732 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
@@ -413,7 +413,11 @@ public class CommandWrapper {
public boolean isUnassignStaff() {
return this.actionName.equalsIgnoreCase("UNASSIGNSTAFF") && this.entityName.equalsIgnoreCase("GROUP");
}
-
+
+ public boolean isAssignStaff() {
+ return this.actionName.equalsIgnoreCase("ASSIGNSTAFF");
+ }
+
public String commandName() {
return this.actionName + "_" + this.entityName;
}
@@ -474,6 +478,10 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("UNDOTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
+ public boolean isAdjustSavingsAccountTransaction() {
+ return this.actionName.equalsIgnoreCase("ADJUSTTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
+ }
+
public boolean isCalendarResource() {
return this.entityName.equalsIgnoreCase("CALENDAR");
}
@@ -543,6 +551,10 @@ public class CommandWrapper {
public boolean isUpdateGroupRole() {
return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("UPDATEROLE");
}
+
+ public boolean isTransferClientsBetweenGroups() {
+ return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("TRANSFERCLIENTS");
+ }
public boolean isClientAssignStaff() {
return this.actionName.equalsIgnoreCase("ASSIGNSTAFF") && this.entityName.equalsIgnoreCase("CLIENT");
@@ -551,6 +563,10 @@ public class CommandWrapper {
public boolean isClientClose() {
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT");
}
+
+ public boolean isClientTransfer() {
+ return this.actionName.equalsIgnoreCase("TRANSFER") && this.entityName.equalsIgnoreCase("CLIENT");
+ }
public boolean isProductMixResource() {
return this.entityName.equalsIgnoreCase("PRODUCTMIX");
@@ -559,4 +575,16 @@ public class CommandWrapper {
public boolean isSchedulerResource() {
return this.entityName.equalsIgnoreCase("SCHEDULER");
}
+
+ public boolean isAccountTransferResource() {
+ return this.entityName.equalsIgnoreCase("ACCOUNTTRANSFER");
+ }
+
+ public boolean isMeetingResource() {
+ return this.entityName.equalsIgnoreCase("MEETING");
+ }
+
+ public boolean isSaveOrUpdateAttendance(){
+ return this.actionName.equalsIgnoreCase("SAVEORUPDATEATTENDANCE");
+ }
}
\ No newline at end of file
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 282259cd5..49d7838ad 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
@@ -342,7 +342,7 @@ public class CommandWrapperBuilder {
this.href = "/clients/" + clientId + "?command=activate&template=true";
return this;
}
-
+
public CommandWrapperBuilder closeClient(final Long clientId) {
this.actionName = "CLOSE";
this.entityName = "CLIENT";
@@ -351,6 +351,15 @@ public class CommandWrapperBuilder {
this.href = "/clients/" + clientId + "?command=close&template=true";
return this;
}
+
+ public CommandWrapperBuilder transferClient(final Long clientId) {
+ this.actionName = "TRANSFER";
+ this.entityName = "CLIENT";
+ this.entityId = clientId;
+ this.clientId = clientId;
+ this.href = "/clientId/" + clientId + "?command=transfer";
+ return this;
+ }
public CommandWrapperBuilder updateClient(final Long clientId) {
this.actionName = "UPDATE";
@@ -372,30 +381,30 @@ public class CommandWrapperBuilder {
}
public CommandWrapperBuilder createDBDatatable(final String json) {
- this.actionName = "CREATE";
- this.entityName = "DATATABLE";
- this.entityId = null;
- this.href = "/datatables/";
- this.json = json;
- return this;
+ this.actionName = "CREATE";
+ this.entityName = "DATATABLE";
+ this.entityId = null;
+ this.href = "/datatables/";
+ this.json = json;
+ return this;
}
public CommandWrapperBuilder updateDBDatatable(final String datatable, final String json) {
- this.actionName = "UPDATE";
- this.entityName = "DATATABLE";
- this.entityId = null;
- this.href = "/datatables/" + datatable;
- this.json = json;
- return this;
+ this.actionName = "UPDATE";
+ this.entityName = "DATATABLE";
+ this.entityId = null;
+ this.href = "/datatables/" + datatable;
+ this.json = json;
+ return this;
}
public CommandWrapperBuilder deleteDBDatatable(final String datatable, final String json) {
- this.actionName = "DELETE";
- this.entityName = "DATATABLE";
- this.entityId = null;
- this.href = "/datatables/" + datatable;
- this.json = json;
- return this;
+ this.actionName = "DELETE";
+ this.entityName = "DATATABLE";
+ this.entityId = null;
+ this.href = "/datatables/" + datatable;
+ this.json = json;
+ return this;
}
public CommandWrapperBuilder createDatatable(final String datatable, final Long apptableId, final Long datatableId) {
@@ -808,6 +817,14 @@ public class CommandWrapperBuilder {
return this;
}
+ public CommandWrapperBuilder createAccountTransfer() {
+ this.actionName = "CREATE";
+ this.entityName = "ACCOUNTTRANSFER";
+ this.entityId = null;
+ this.href = "/accounttransfers";
+ return this;
+ }
+
public CommandWrapperBuilder savingsAccountDeposit(final Long accountId) {
this.actionName = "DEPOSIT";
this.entityName = "SAVINGSACCOUNT";
@@ -837,6 +854,17 @@ public class CommandWrapperBuilder {
return this;
}
+ public CommandWrapperBuilder adjustSavingsAccountTransaction(final Long accountId, final Long transactionId) {
+ this.actionName = "ADJUSTTRANSACTION";
+ this.entityName = "SAVINGSACCOUNT";
+ this.savingsId = accountId;
+ this.entityId = accountId;
+ this.subentityId = transactionId;
+ this.transactionId = transactionId.toString();
+ this.href = "/savingsaccounts/" + accountId + "/transactions/" + transactionId + "?command=modify";
+ return this;
+ }
+
public CommandWrapperBuilder savingsAccountInterestCalculation(final Long accountId) {
this.actionName = "CALCULATEINTEREST";
this.entityName = "SAVINGSACCOUNT";
@@ -945,7 +973,7 @@ public class CommandWrapperBuilder {
this.href = "/groups/" + groupId + "?command=activate";
return this;
}
-
+
public CommandWrapperBuilder saveGroupCollectionSheet(final Long groupId) {
this.actionName = "SAVECOLLECTIONSHEET";
this.entityName = "GROUP";
@@ -954,7 +982,7 @@ public class CommandWrapperBuilder {
this.href = "/groups/" + groupId + "?command=saveCollectionSheet";
return this;
}
-
+
public CommandWrapperBuilder deleteGroup(final Long groupId) {
this.actionName = "DELETE";
this.entityName = "GROUP";
@@ -981,6 +1009,15 @@ public class CommandWrapperBuilder {
this.href = "/groups/" + groupId + "?command=disassociateClients";
return this;
}
+
+ public CommandWrapperBuilder transferClientsBetweenGroups(final Long sourceGroupId) {
+ this.actionName = "TRANSFERCLIENTS";
+ this.entityName = "GROUP";
+ this.entityId = sourceGroupId;
+ this.groupId = sourceGroupId;
+ this.href = "/groups/" + sourceGroupId + "?command=transferClients";
+ return this;
+ }
public CommandWrapperBuilder unassignGroupStaff(final Long groupId) {
this.actionName = "UNASSIGNSTAFF";
@@ -991,6 +1028,15 @@ public class CommandWrapperBuilder {
return this;
}
+ public CommandWrapperBuilder assignGroupStaff(final Long groupId) {
+ this.actionName = "ASSIGNSTAFF";
+ this.entityName = "GROUP";
+ this.entityId = groupId;
+ this.groupId = groupId;
+ this.href = "/groups/" + groupId + "?command=assignStaff";
+ return this;
+ }
+
public CommandWrapperBuilder createCollateral(final Long loanId) {
this.actionName = "CREATE";
this.entityName = "COLLATERAL";
@@ -1089,7 +1135,7 @@ public class CommandWrapperBuilder {
this.href = "/accountingrules/" + accountingRuleId;
return this;
}
-
+
public CommandWrapperBuilder createHoliday() {
this.actionName = "CREATE";
this.entityName = "HOLIDAY";
@@ -1103,7 +1149,7 @@ public class CommandWrapperBuilder {
this.entityName = "GROUP";
this.groupId = groupId;
this.entityId = null;
- this.href = "/groups/"+groupId+"?command=assignRole";
+ this.href = "/groups/" + groupId + "?command=assignRole";
return this;
}
@@ -1112,7 +1158,7 @@ public class CommandWrapperBuilder {
this.entityName = "GROUP";
this.groupId = groupId;
this.entityId = roleId;
- this.href = "/groups/"+groupId+"?command=unassignRole";
+ this.href = "/groups/" + groupId + "?command=unassignRole";
return this;
}
@@ -1121,10 +1167,10 @@ public class CommandWrapperBuilder {
this.entityName = "GROUP";
this.groupId = groupId;
this.entityId = roleId;
- this.href = "/groups/"+groupId+"?command=updateRole";
+ this.href = "/groups/" + groupId + "?command=updateRole";
return this;
}
-
+
public CommandWrapperBuilder unassignClientStaff(final Long clientId) {
this.actionName = "UNASSIGNSTAFF";
this.entityName = "CLIENT";
@@ -1160,7 +1206,7 @@ public class CommandWrapperBuilder {
this.href = "/loanproducts/" + productId + "/productmix";
return this;
}
-
+
public CommandWrapperBuilder deleteProductMix(final Long productId) {
this.actionName = "DELETE";
this.entityName = "PRODUCTMIX";
@@ -1169,17 +1215,52 @@ public class CommandWrapperBuilder {
this.href = "/loanproducts/" + productId + "/productmix";
return this;
}
-
+
public CommandWrapperBuilder withProduct(final Long productId) {
this.productId = productId;
return this;
}
-
+
public CommandWrapperBuilder updateJobDetail(final Long jobId) {
this.actionName = "UPDATE";
this.entityName = "SCHEDULER";
this.entityId = jobId;
- this.href = "/updateJobDetail/" + jobId+"/updateJobDetail";
+ this.href = "/updateJobDetail/" + jobId + "/updateJobDetail";
+ return this;
+ }
+
+ public CommandWrapperBuilder createMeeting(final String supportedEntityType, final Long supportedEntityId) {
+ this.actionName = "CREATE";
+ this.entityName = "MEETING";
+ this.supportedEntityType = supportedEntityType;
+ this.supportedEntityId = supportedEntityId;
+ this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings";
+ return this;
+ }
+
+ public CommandWrapperBuilder updateMeeting(final String supportedEntityType, final Long supportedEntityId, final Long meetingId) {
+ this.actionName = "UPDATE";
+ this.entityName = "MEETING";
+ this.entityId = meetingId;
+ this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId;
+ return this;
+ }
+
+ public CommandWrapperBuilder deleteMeeting(final String supportedEntityType, final Long supportedEntityId, final Long meetingId) {
+ this.actionName = "DELETE";
+ this.entityName = "MEETING";
+ this.entityId = meetingId;
+ this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId;
+ return this;
+ }
+
+ public CommandWrapperBuilder saveOrUpdateAttendance(final Long entityId, final String supportedEntityType, final Long supportedEntityId) {
+ this.actionName = "SAVEORUPDATEATTENDANCE";
+ this.entityName = "MEETING";
+ this.entityId = entityId;
+ this.supportedEntityType = supportedEntityType;
+ this.supportedEntityId = supportedEntityId;
+ this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + entityId + "?command=saveOrUpdateAttendance";
return this;
}
}
\ No newline at end of file
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 80df9e704..b8822b31c 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
@@ -105,7 +105,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) {
NewCommandSourceHandler handler = null;
- if (wrapper.isConfigurationResource()) {
+ if (wrapper.isAccountTransferResource()) {
+ if (wrapper.isCreate()) {
+ handler = this.applicationContext.getBean("createAccountTransferCommandHandler", NewCommandSourceHandler.class);
+ } else {
+ throw new UnsupportedCommandException(wrapper.commandName());
+ }
+ }
+ else if (wrapper.isConfigurationResource()) {
handler = this.applicationContext.getBean("updateGlobalConfigurationCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDatatableResource()) {
if (wrapper.isCreateDatatable()) {
@@ -163,6 +170,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("assignClientStaffCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isClientClose()) {
handler = this.applicationContext.getBean("closeClientCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isClientTransfer()) {
+ handler = this.applicationContext.getBean("transferClientBetweenBranchesCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -405,6 +414,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("applyAnnualFeeSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountUndoTransaction()) {
handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isAdjustSavingsAccountTransaction()) {
+ handler = this.applicationContext.getBean("savingsTransactionAdjustmentCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -441,6 +452,10 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("unassignRoleCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateGroupRole()) {
handler = this.applicationContext.getBean("updateGroupRoleCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isAssignStaff()) {
+ handler = this.applicationContext.getBean("assignGroupStaffCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isTransferClientsBetweenGroups()) {
+ handler = this.applicationContext.getBean("transferClientsBetweenGroupsCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -511,6 +526,18 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
+ } else if (wrapper.isMeetingResource()) {
+ if (wrapper.isCreate()) {
+ handler = this.applicationContext.getBean("createMeetingCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isUpdate()) {
+ handler = this.applicationContext.getBean("updateMeetingCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isDelete()) {
+ handler = this.applicationContext.getBean("deleteMeetingCommandHandler", NewCommandSourceHandler.class);
+ } else if (wrapper.isSaveOrUpdateAttendance()) {
+ handler = this.applicationContext.getBean("updateMeetingAttendanceCommandHandler", NewCommandSourceHandler.class);
+ } else {
+ throw new UnsupportedCommandException(wrapper.commandName());
+ }
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/domain/CodeValue.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/domain/CodeValue.java
index 7efc1b33b..549722db0 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/domain/CodeValue.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/domain/CodeValue.java
@@ -22,72 +22,85 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
-@Table(name = "m_code_value", uniqueConstraints = { @UniqueConstraint(columnNames = { "code_id", "code_value" }, name = "code_value_duplicate") })
+@Table(name = "m_code_value", uniqueConstraints = { @UniqueConstraint(columnNames = {
+ "code_id", "code_value" }, name = "code_value_duplicate") })
public class CodeValue extends AbstractPersistable {
- @Column(name = "code_value", length = 100)
- private String label;
+ @Column(name = "code_value", length = 100)
+ private String label;
- @Column(name = "order_position")
- private int position;
+ @Column(name = "order_position")
+ private int position;
- @SuppressWarnings("unused")
- @ManyToOne
- @JoinColumn(name = "code_id", nullable = false)
- private Code code;
+ @ManyToOne
+ @JoinColumn(name = "code_id", nullable = false)
+ private Code code;
- public static CodeValue createNew(Code code, final String label, final int position) {
- return new CodeValue(code, label, position);
- }
+ public static CodeValue createNew(final Code code, final String label,
+ final int position) {
+ return new CodeValue(code, label, position);
+ }
- protected CodeValue() {
- //
- }
+ protected CodeValue() {
+ //
+ }
- private CodeValue(final Code code, final String label, final int position) {
- this.code = code;
- this.label = StringUtils.defaultIfEmpty(label, null);
- this.position = position;
- }
+ private CodeValue(final Code code, final String label, final int position) {
+ this.code = code;
+ this.label = StringUtils.defaultIfEmpty(label, null);
+ this.position = position;
+ }
- public String label() {
- return label;
- }
+ public String label() {
+ return this.label;
+ }
- public int position() {
- return position;
- }
-
- public static CodeValue fromJson(Code code, final JsonCommand command) {
+ public int position() {
+ return this.position;
+ }
- final String label = command.stringValueOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue());
- Integer position = command.integerValueSansLocaleOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue());
- if(position == null) position = new Integer(0);
- return new CodeValue(code, label, position.intValue());
- }
-
- public Map update(final JsonCommand command) {
+ public static CodeValue fromJson(final Code code, final JsonCommand command) {
- final Map actualChanges = new LinkedHashMap(2);
+ final String label = command
+ .stringValueOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.NAME
+ .getValue());
+ Integer position = command
+ .integerValueSansLocaleOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.POSITION
+ .getValue());
+ if (position == null) {
+ position = new Integer(0);
+ }
+ return new CodeValue(code, label, position.intValue());
+ }
- final String labelParamName = CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue();
- if (command.isChangeInStringParameterNamed(labelParamName, this.label)) {
- final String newValue = command.stringValueOfParameterNamed(labelParamName);
- actualChanges.put(labelParamName, newValue);
- this.label = StringUtils.defaultIfEmpty(newValue, null);
- }
-
- final String positionParamName = CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue();
- if (command.isChangeInIntegerSansLocaleParameterNamed(positionParamName, this.position)) {
- final Integer newValue = command.integerValueSansLocaleOfParameterNamed(positionParamName);
- actualChanges.put(positionParamName, newValue);
- this.position = newValue.intValue();
- }
-
- return actualChanges;
- }
+ public Map update(final JsonCommand command) {
- public CodeValueData toData() {
- return CodeValueData.instance(this.getId(), this.label, this.position);
- }
+ final Map actualChanges = new LinkedHashMap(
+ 2);
+
+ final String labelParamName = CODEVALUE_JSON_INPUT_PARAMS.NAME
+ .getValue();
+ if (command.isChangeInStringParameterNamed(labelParamName, this.label)) {
+ final String newValue = command
+ .stringValueOfParameterNamed(labelParamName);
+ actualChanges.put(labelParamName, newValue);
+ this.label = StringUtils.defaultIfEmpty(newValue, null);
+ }
+
+ final String positionParamName = CODEVALUE_JSON_INPUT_PARAMS.POSITION
+ .getValue();
+ if (command.isChangeInIntegerSansLocaleParameterNamed(
+ positionParamName, this.position)) {
+ final Integer newValue = command
+ .integerValueSansLocaleOfParameterNamed(positionParamName);
+ actualChanges.put(positionParamName, newValue);
+ this.position = newValue.intValue();
+ }
+
+ return actualChanges;
+ }
+
+ public CodeValueData toData() {
+ return CodeValueData.instance(getId(), this.label, this.position);
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ExternalService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ExternalService.java
index 1fca5d62d..ec8b30055 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ExternalService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ExternalService.java
@@ -14,10 +14,9 @@ import javax.persistence.UniqueConstraint;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
-@Table(name = "c_external_service", uniqueConstraints = { @UniqueConstraint(columnNames = ("name")) })
+@Table(name = "c_external_service", uniqueConstraints = { @UniqueConstraint(columnNames = "name") })
public class ExternalService extends AbstractPersistable {
- @SuppressWarnings("unused")
@Column(name = "name", nullable = false)
private String name;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/GlobalConfigurationProperty.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/GlobalConfigurationProperty.java
index e9dbff130..f40370be7 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/GlobalConfigurationProperty.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/GlobalConfigurationProperty.java
@@ -15,7 +15,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "c_configuration")
public class GlobalConfigurationProperty extends AbstractPersistable {
- @SuppressWarnings("unused")
@Column(name = "name", nullable = false)
private final String name;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java
index eda5217d5..d6c81ed8a 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java
@@ -306,6 +306,10 @@ public final class JsonCommand {
final String value = this.fromApiJsonHelper.extractStringNamed(parameterName, parsedCommand);
return StringUtils.defaultIfEmpty(value, "");
}
+
+ public String stringValueOfParameterNamedAllowingNull(final String parameterName) {
+ return this.fromApiJsonHelper.extractStringNamed(parameterName, parsedCommand);
+ }
public boolean isChangeInBigDecimalParameterNamedDefaultingZeroToNull(final String parameterName, final BigDecimal existingValue) {
boolean isChanged = false;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java
index a6f66c1f9..0e9c76e28 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java
@@ -128,6 +128,7 @@ public class DataValidatorBuilder {
return this;
}
+ /***FIXME: Vishwas, why does this method have a parameter? Seems wrong***/
public DataValidatorBuilder trueOrFalseRequired(final boolean trueOfFalseFieldProvided) {
if (!trueOfFalseFieldProvided && !ignoreNullValue) {
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/ScheduledJobDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/ScheduledJobDetail.java
index ac391a344..48b6ba191 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/ScheduledJobDetail.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/ScheduledJobDetail.java
@@ -44,7 +44,6 @@ public class ScheduledJobDetail extends AbstractPersistable {
@Temporal(TemporalType.TIMESTAMP)
private Date previousRunStartTime;
- @SuppressWarnings("unused")
@Column(name = "next_run_time")
@Temporal(TemporalType.TIMESTAMP)
private Date nextRunTime;
@@ -69,6 +68,9 @@ public class ScheduledJobDetail extends AbstractPersistable {
@Column(name = "scheduler_group")
private Short schedulerGroup;
+ @Column(name = "is_misfired")
+ private boolean triggerMisfired;
+
protected ScheduledJobDetail() {
}
@@ -109,11 +111,15 @@ public class ScheduledJobDetail extends AbstractPersistable {
this.previousRunStartTime = previousRunStartTime;
}
+ public Date getNextRunTime() {
+ return this.nextRunTime;
+ }
+
public void updateNextRunTime(final Date nextRunTime) {
this.nextRunTime = nextRunTime;
}
- public void updateJobKey(String jobKey) {
+ public void updateJobKey(final String jobKey) {
this.jobKey = jobKey;
}
@@ -125,7 +131,7 @@ public class ScheduledJobDetail extends AbstractPersistable {
return this.currentlyRunning;
}
- public void updateCurrentlyRunningStatus(boolean currentlyRunning) {
+ public void updateCurrentlyRunningStatus(final boolean currentlyRunning) {
this.currentlyRunning = currentlyRunning;
}
@@ -152,4 +158,12 @@ public class ScheduledJobDetail extends AbstractPersistable {
return actualChanges;
}
+ public boolean isTriggerMisfired() {
+ return this.triggerMisfired;
+ }
+
+ public void updateTriggerMisfired(boolean triggerMisfired) {
+ this.triggerMisfired = triggerMisfired;
+ }
+
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/ScheduledJobDetailRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
index c673f7ea0..50cb0dc5f 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
@@ -1,7 +1,10 @@
package org.mifosplatform.infrastructure.jobs.domain;
+import javax.persistence.LockModeType;
+
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@@ -14,4 +17,9 @@ public interface ScheduledJobDetailRepository extends JpaRepository {
- @SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "job_id")
+ @SuppressWarnings("unused")
private ScheduledJobDetail scheduledJobDetail;
- @SuppressWarnings("unused")
@Column(name = "version")
+ @SuppressWarnings("unused")
private Long version;
- @SuppressWarnings("unused")
@Column(name = "start_time")
@Temporal(TemporalType.TIMESTAMP)
+ @SuppressWarnings("unused")
private Date startTime;
- @SuppressWarnings("unused")
@Column(name = "end_time")
@Temporal(TemporalType.TIMESTAMP)
+ @SuppressWarnings("unused")
private Date endTime;
- @SuppressWarnings("unused")
@Column(name = "status")
+ @SuppressWarnings("unused")
private String status;
- @SuppressWarnings("unused")
@Column(name = "error_message")
+ @SuppressWarnings("unused")
private String errorMessage;
- @SuppressWarnings("unused")
@Column(name = "trigger_type")
+ @SuppressWarnings("unused")
private String triggerType;
- @SuppressWarnings("unused")
@Column(name = "error_log")
+ @SuppressWarnings("unused")
private String errorLog;
public ScheduledJobRunHistory() {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/SchedulerDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/SchedulerDetail.java
new file mode 100755
index 000000000..3cb68eee3
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/SchedulerDetail.java
@@ -0,0 +1,49 @@
+package org.mifosplatform.infrastructure.jobs.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "scheduler_detail")
+public class SchedulerDetail extends AbstractPersistable {
+
+ @Column(name = "execute_misfired_jobs")
+ private boolean executeInstructionForMisfiredJobs;
+
+ @Column(name = "is_suspended")
+ private boolean suspended;
+
+ @Column(name = "reset_scheduler_on_bootup")
+ private boolean resetSchedulerOnBootup;
+
+ protected SchedulerDetail() {
+
+ }
+
+ public boolean isExecuteInstructionForMisfiredJobs() {
+ return this.executeInstructionForMisfiredJobs;
+ }
+
+ public void updateExecuteInstructionForMisfiredJobs(boolean executeInstructionForMisfiredJobs) {
+ this.executeInstructionForMisfiredJobs = executeInstructionForMisfiredJobs;
+ }
+
+ public boolean isSuspended() {
+ return this.suspended;
+ }
+
+ public void updateSuspendedState(boolean suspended) {
+ this.suspended = suspended;
+ }
+
+ public boolean isResetSchedulerOnBootup() {
+ return this.resetSchedulerOnBootup;
+ }
+
+ public void updateResetSchedulerOnBootup(boolean resetSchedulerOnBootup) {
+ this.resetSchedulerOnBootup = resetSchedulerOnBootup;
+ }
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/SchedulerDetailRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/SchedulerDetailRepository.java
new file mode 100755
index 000000000..ee3d8cd8a
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/domain/SchedulerDetailRepository.java
@@ -0,0 +1,8 @@
+package org.mifosplatform.infrastructure.jobs.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface SchedulerDetailRepository extends JpaRepository, JpaSpecificationExecutor {
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java
index 8052140a7..976003921 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java
@@ -4,7 +4,7 @@ public enum JobName {
UPDATE_LOAN_SUMMARY("Update loan Summary"), UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), UPDATE_LOAN_PAID_IN_ADVANCE(
"Update Loan Paid In Advance"), APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS(
- "Apply Holidays To Loans");
+ "Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS("Post Interest For Savings");
private final String name;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobRegisterServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobRegisterServiceImpl.java
index c395cfd71..405e8d156 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobRegisterServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobRegisterServiceImpl.java
@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
@@ -15,6 +14,7 @@ import org.mifosplatform.infrastructure.core.exception.PlatformInternalServerExc
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.jobs.annotation.CronMethodParser;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail;
+import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetail;
import org.mifosplatform.infrastructure.jobs.exception.JobNotFoundException;
import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
import org.quartz.JobDataMap;
@@ -24,6 +24,7 @@ import org.quartz.JobListener;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
+import org.quartz.TriggerListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -50,8 +51,6 @@ public class JobRegisterServiceImpl implements JobRegisterService {
private final TenantDetailsService tenantDetailsService;
- private final HashMap schedulerStatus = new HashMap(2);
-
private final HashMap schedulers = new HashMap(4);
@Autowired
@@ -68,24 +67,32 @@ public class JobRegisterServiceImpl implements JobRegisterService {
for (MifosPlatformTenant tenant : allTenants) {
ThreadLocalContextUtil.setTenant(tenant);
List scheduledJobDetails = this.schedularWritePlatformService.retrieveAllJobs();
- schedulerStatus.put(tenant.getId(), true);
for (ScheduledJobDetail jobDetails : scheduledJobDetails) {
scheduleJob(jobDetails);
+ jobDetails.updateTriggerMisfired(false);
this.schedularWritePlatformService.saveOrUpdate(jobDetails);
}
+ SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
+ if (schedulerDetail.isResetSchedulerOnBootup()) {
+ schedulerDetail.updateSuspendedState(false);
+ this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
+ }
}
}
- public void executeJob(final ScheduledJobDetail scheduledJobDetail) {
+ public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType) {
try {
final JobDataMap jobDataMap = new JobDataMap();
- jobDataMap.put(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE, SchedulerServiceConstants.TRIGGER_TYPE_APPLICATION);
+ if (triggerType == null) {
+ triggerType = SchedulerServiceConstants.TRIGGER_TYPE_APPLICATION;
+ }
+ jobDataMap.put(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE, triggerType);
jobDataMap.put(SchedulerServiceConstants.TENANT_IDENTIFIER, ThreadLocalContextUtil.getTenant().getTenantIdentifier());
String key = scheduledJobDetail.getJobKey();
JobKey jobKey = constructJobKey(key);
String schedulerName = getSchedulerName(scheduledJobDetail);
Scheduler scheduler = schedulers.get(schedulerName);
- if (scheduler == null || scheduler.isInStandbyMode() || !scheduler.checkExists(jobKey)) {
+ if (scheduler == null || !scheduler.checkExists(jobKey)) {
JobDetail jobDetail = createJobDetail(scheduledJobDetail);
String tempSchedulerName = "temp" + scheduledJobDetail.getId();
List> listenerClasses = new ArrayList>(2);
@@ -101,7 +108,6 @@ public class JobRegisterServiceImpl implements JobRegisterService {
}
} catch (Exception e) {
- e.printStackTrace();
throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", "Job execution failed for job with id:"
+ scheduledJobDetail.getId(), scheduledJobDetail.getId());
}
@@ -128,32 +134,48 @@ public class JobRegisterServiceImpl implements JobRegisterService {
@Override
public void pauseScheduler() {
- schedulerStatus.put(ThreadLocalContextUtil.getTenant().getId(), false);
- for (Map.Entry schedulerEntry : this.schedulers.entrySet()) {
- try {
- String schedulerName = schedulerEntry.getValue().getSchedulerName();
- if (isCurrentTenantsScheduler(schedulerName)) {
- schedulerEntry.getValue().standby();
- }
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
+ SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
+ if (!schedulerDetail.isSuspended()) {
+ schedulerDetail.updateSuspendedState(true);
+ this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
}
}
@Override
public void startScheduler() {
- for (Map.Entry schedulerEntry : this.schedulers.entrySet()) {
- try {
- String schedulerName = schedulerEntry.getValue().getSchedulerName();
- if (isCurrentTenantsScheduler(schedulerName)) {
- schedulerEntry.getValue().start();
+ SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
+ if (schedulerDetail.isSuspended()) {
+ schedulerDetail.updateSuspendedState(false);
+ this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
+ if (schedulerDetail.isExecuteInstructionForMisfiredJobs()) {
+ List scheduledJobDetails = this.schedularWritePlatformService.retrieveAllJobs();
+ for (ScheduledJobDetail jobDetail : scheduledJobDetails) {
+ if (jobDetail.isTriggerMisfired()) {
+ if (jobDetail.isActiveSchedular()) {
+ executeJob(jobDetail, SchedulerServiceConstants.TRIGGER_TYPE_CRON);
+ }
+ String schedulerName = getSchedulerName(jobDetail);
+ Scheduler scheduler = schedulers.get(schedulerName);
+ if (scheduler != null) {
+ String key = jobDetail.getJobKey();
+ JobKey jobKey = constructJobKey(key);
+ try {
+ List extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
+ for (Trigger trigger : triggers) {
+ if (trigger.getNextFireTime() != null && trigger.getNextFireTime().after(jobDetail.getNextRunTime())) {
+ jobDetail.updateNextRunTime(trigger.getNextFireTime());
+ }
+ }
+ } catch (SchedulerException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ jobDetail.updateTriggerMisfired(false);
+ this.schedularWritePlatformService.saveOrUpdate(jobDetail);
+ }
}
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
}
}
- schedulerStatus.put(ThreadLocalContextUtil.getTenant().getId(), true);
}
@Override
@@ -166,23 +188,12 @@ public class JobRegisterServiceImpl implements JobRegisterService {
public void executeJob(final Long jobId) {
ScheduledJobDetail scheduledJobDetail = this.schedularWritePlatformService.findByJobId(jobId);
if (scheduledJobDetail == null) { throw new JobNotFoundException(String.valueOf(jobId)); }
- executeJob(scheduledJobDetail);
+ executeJob(scheduledJobDetail, null);
}
@Override
public boolean isSchedulerRunning() {
- return this.schedulerStatus.get(ThreadLocalContextUtil.getTenant().getId());
- }
-
- private boolean isCurrentTenantsScheduler(final String schedulerName) {
- int beginIndex = SchedulerServiceConstants.SCHEDULER.length();
- int endIndex = schedulerName.length();
- if (schedulerName.contains(SchedulerServiceConstants.SCHEDULER_GROUP)) {
- endIndex = schedulerName.indexOf(SchedulerServiceConstants.SCHEDULER_GROUP);
- }
- String tenantId = schedulerName.substring(beginIndex, endIndex);
- MifosPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
- return tenant.getId() == Long.valueOf(tenantId);
+ return !this.schedularWritePlatformService.retriveSchedulerDetail().isSuspended();
}
private void scheduleJob(final ScheduledJobDetail scheduledJobDetails) {
@@ -195,9 +206,6 @@ public class JobRegisterServiceImpl implements JobRegisterService {
JobDetail jobDetail = createJobDetail(scheduledJobDetails);
Trigger trigger = createTrigger(scheduledJobDetails, jobDetail);
Scheduler scheduler = getScheduler(scheduledJobDetails);
- if (!this.schedulerStatus.get(ThreadLocalContextUtil.getTenant().getId()) && !scheduler.isInStandbyMode()) {
- scheduler.standby();
- }
scheduler.scheduleJob(jobDetail, trigger);
scheduledJobDetails.updateJobKey(getJobKeyAsString(jobDetail.getKey()));
scheduledJobDetails.updateNextRunTime(trigger.getNextFireTime());
@@ -253,6 +261,9 @@ public class JobRegisterServiceImpl implements JobRegisterService {
listenerClasses.add(SchedulerJobListener.class);
}
schedulerFactoryBean.setGlobalJobListeners(getGlobalListener(listenerClasses));
+ TriggerListener globalTriggerListener = applicationContext.getBean("schedulerTriggerListener", TriggerListener.class);
+ TriggerListener[] globalTriggerListeners = { globalTriggerListener };
+ schedulerFactoryBean.setGlobalTriggerListeners(globalTriggerListeners);
Properties quartzProperties = new Properties();
quartzProperties.put(SchedulerFactoryBean.PROP_THREAD_COUNT, Integer.toString(noOfThreads));
schedulerFactoryBean.setQuartzProperties(quartzProperties);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedularWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedularWritePlatformService.java
index b5c6a680c..193294ee0 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedularWritePlatformService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedularWritePlatformService.java
@@ -6,6 +6,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobRunHistory;
+import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetail;
public interface SchedularWritePlatformService {
@@ -21,6 +22,12 @@ public interface SchedularWritePlatformService {
public ScheduledJobDetail findByJobId(Long jobId);
- CommandProcessingResult updateJobDetail(Long jobId, JsonCommand command);
+ public CommandProcessingResult updateJobDetail(Long jobId, JsonCommand command);
+
+ public SchedulerDetail retriveSchedulerDetail();
+
+ public void updateSchedulerDetail(final SchedulerDetail schedulerDetail);
+
+ public boolean processJobDetailForExecution(String jobKey, String triggerType);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java
index 74a3b96a9..b686ef849 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java
@@ -1,5 +1,6 @@
package org.mifosplatform.infrastructure.jobs.service;
+import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -11,6 +12,8 @@ import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetailRepository;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobRunHistory;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobRunHistoryRepository;
+import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetail;
+import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetailRepository;
import org.mifosplatform.infrastructure.jobs.exception.JobNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -23,13 +26,17 @@ public class SchedularWritePlatformServiceJpaRepositoryImpl implements Schedular
private final ScheduledJobRunHistoryRepository scheduledJobRunHistoryRepository;
+ private final SchedulerDetailRepository schedulerDetailRepository;
+
private final JobDetailDataValidator dataValidator;
@Autowired
public SchedularWritePlatformServiceJpaRepositoryImpl(final ScheduledJobDetailRepository scheduledJobDetailsRepository,
- final ScheduledJobRunHistoryRepository scheduledJobRunHistoryRepository, final JobDetailDataValidator dataValidator) {
+ final ScheduledJobRunHistoryRepository scheduledJobRunHistoryRepository, final JobDetailDataValidator dataValidator,
+ final SchedulerDetailRepository schedulerDetailRepository) {
this.scheduledJobDetailsRepository = scheduledJobDetailsRepository;
this.scheduledJobRunHistoryRepository = scheduledJobRunHistoryRepository;
+ this.schedulerDetailRepository = schedulerDetailRepository;
this.dataValidator = dataValidator;
}
@@ -71,6 +78,22 @@ public class SchedularWritePlatformServiceJpaRepositoryImpl implements Schedular
return this.scheduledJobDetailsRepository.findByJobId(jobId);
}
+ @Override
+ @Transactional
+ public void updateSchedulerDetail(final SchedulerDetail schedulerDetail) {
+ this.schedulerDetailRepository.save(schedulerDetail);
+ }
+
+ @Override
+ public SchedulerDetail retriveSchedulerDetail() {
+ SchedulerDetail schedulerDetail = null;
+ List schedulerDetailList = schedulerDetailRepository.findAll();
+ if (schedulerDetailList != null) {
+ schedulerDetail = schedulerDetailList.get(0);
+ }
+ return schedulerDetail;
+ }
+
@Transactional
@Override
public CommandProcessingResult updateJobDetail(final Long jobId, final JsonCommand command) {
@@ -89,4 +112,24 @@ public class SchedularWritePlatformServiceJpaRepositoryImpl implements Schedular
}
+ @Transactional
+ @Override
+ public boolean processJobDetailForExecution(String jobKey, String triggerType) {
+ boolean isStopExecution = false;
+ final ScheduledJobDetail scheduledJobDetail = scheduledJobDetailsRepository.findByJobKeyWithLock(jobKey);
+ if (scheduledJobDetail.isCurrentlyRunning()
+ || (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && (scheduledJobDetail.getNextRunTime().after(new Date())))) {
+ isStopExecution = true;
+ }
+ final SchedulerDetail schedulerDetail = retriveSchedulerDetail();
+ if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && schedulerDetail.isSuspended()) {
+ scheduledJobDetail.updateTriggerMisfired(true);
+ isStopExecution = true;
+ } else if (!isStopExecution) {
+ scheduledJobDetail.updateCurrentlyRunningStatus(true);
+ }
+ scheduledJobDetailsRepository.save(scheduledJobDetail);
+ return isStopExecution;
+ }
+
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerJobListener.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerJobListener.java
index a83811f07..542397498 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerJobListener.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerJobListener.java
@@ -2,12 +2,9 @@ package org.mifosplatform.infrastructure.jobs.service;
import java.util.Date;
-import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobRunHistory;
-import org.mifosplatform.infrastructure.jobs.exception.JobInProcessExecution;
-import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
@@ -31,12 +28,9 @@ public class SchedulerJobListener implements JobListener {
private final SchedularWritePlatformService schedularService;
- private final TenantDetailsService tenantDetailsService;
-
@Autowired
- public SchedulerJobListener(final SchedularWritePlatformService schedularService, final TenantDetailsService tenantDetailsService) {
+ public SchedulerJobListener(final SchedularWritePlatformService schedularService) {
this.schedularService = schedularService;
- this.tenantDetailsService = tenantDetailsService;
}
@Override
@@ -45,16 +39,7 @@ public class SchedulerJobListener implements JobListener {
}
@Override
- public void jobToBeExecuted(final JobExecutionContext context) {
- String tenantIdentifier = context.getTrigger().getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
- MifosPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier);
- ThreadLocalContextUtil.setTenant(tenant);
- JobKey key = context.getTrigger().getJobKey();
- String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup();
- final ScheduledJobDetail scheduledJobDetail = schedularService.findByJobKey(jobKey);
- if (scheduledJobDetail.isCurrentlyRunning()) { throw new JobInProcessExecution(scheduledJobDetail.getJobName()); }
- scheduledJobDetail.updateCurrentlyRunningStatus(true);
- this.schedularService.saveOrUpdate(scheduledJobDetail);
+ public void jobToBeExecuted(@SuppressWarnings("unused") final JobExecutionContext context) {
}
@@ -93,7 +78,8 @@ public class SchedulerJobListener implements JobListener {
if (context.getMergedJobDataMap().containsKey(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE)) {
triggerType = context.getMergedJobDataMap().getString(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE);
}
- if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON) {
+ if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && trigger.getNextFireTime() != null
+ && trigger.getNextFireTime().after(scheduledJobDetails.getNextRunTime())) {
scheduledJobDetails.updateNextRunTime(trigger.getNextFireTime());
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerTriggerListener.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerTriggerListener.java
new file mode 100755
index 000000000..b33daeeae
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerTriggerListener.java
@@ -0,0 +1,66 @@
+package org.mifosplatform.infrastructure.jobs.service;
+
+import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
+import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
+import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobKey;
+import org.quartz.Trigger;
+import org.quartz.Trigger.CompletedExecutionInstruction;
+import org.quartz.TriggerListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SchedulerTriggerListener implements TriggerListener {
+
+ private final String name = "Global trigger Listner";
+
+ private final SchedularWritePlatformService schedularService;
+
+ private final TenantDetailsService tenantDetailsService;
+
+ @Autowired
+ public SchedulerTriggerListener(final SchedularWritePlatformService schedularService, final TenantDetailsService tenantDetailsService) {
+ this.schedularService = schedularService;
+ this.tenantDetailsService = tenantDetailsService;
+
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public void triggerFired(@SuppressWarnings("unused") Trigger trigger, @SuppressWarnings("unused") JobExecutionContext context) {
+
+ }
+
+ @Override
+ public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
+
+ String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
+ MifosPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier);
+ ThreadLocalContextUtil.setTenant(tenant);
+ JobKey key = trigger.getJobKey();
+ String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup();
+ String triggerType = SchedulerServiceConstants.TRIGGER_TYPE_CRON;
+ if (context.getMergedJobDataMap().containsKey(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE)) {
+ triggerType = context.getMergedJobDataMap().getString(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE);
+ }
+ return schedularService.processJobDetailForExecution(jobKey, triggerType);
+ }
+
+ @Override
+ public void triggerMisfired(@SuppressWarnings("unused") Trigger trigger) {
+
+ }
+
+ @Override
+ public void triggerComplete(@SuppressWarnings("unused") Trigger trigger, @SuppressWarnings("unused") JobExecutionContext context,
+ @SuppressWarnings("unused") CompletedExecutionInstruction triggerInstructionCode) {
+
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/Holiday.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/Holiday.java
index 3fd2487e4..b1eba52b1 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/Holiday.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/Holiday.java
@@ -30,7 +30,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_holiday", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "holiday_name") })
public class Holiday extends AbstractPersistable {
- @SuppressWarnings("unused")
@Column(name = "name", unique = true, nullable = false, length = 100)
private String name;
@@ -49,7 +48,6 @@ public class Holiday extends AbstractPersistable {
@Column(name = "processed", nullable = false)
private boolean processed;
- @SuppressWarnings("unused")
@Column(name = "description", length = 100)
private String description;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/data/CurrencyData.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/data/CurrencyData.java
index f5621a9f0..52730b434 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/data/CurrencyData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/data/CurrencyData.java
@@ -13,6 +13,7 @@ public class CurrencyData {
private final String code;
private final String name;
private final int decimalPlaces;
+ private final Integer inMultiplesOf;
private final String displaySymbol;
@SuppressWarnings("unused")
private final String nameCode;
@@ -20,13 +21,14 @@ public class CurrencyData {
private final String displayLabel;
public static CurrencyData blank() {
- return new CurrencyData("", "", 0, "", "");
+ return new CurrencyData("", "", 0,0, "", "");
}
- public CurrencyData(final String code, final String name, final int decimalPlaces, final String displaySymbol, final String nameCode) {
+ public CurrencyData(final String code, final String name, final int decimalPlaces,final Integer inMultiplesOf, final String displaySymbol, final String nameCode) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
+ this.inMultiplesOf = inMultiplesOf;
this.displaySymbol = displaySymbol;
this.nameCode = nameCode;
this.displayLabel = generateDisplayLabel();
@@ -40,6 +42,10 @@ public class CurrencyData {
return this.decimalPlaces;
}
+ public Integer currencyInMultiplesOf() {
+ return this.inMultiplesOf;
+ }
+
@Override
public boolean equals(Object obj) {
CurrencyData currencyData = (CurrencyData) obj;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/ApplicationCurrency.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/ApplicationCurrency.java
index bdf28007d..e7441e151 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/ApplicationCurrency.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/ApplicationCurrency.java
@@ -23,6 +23,9 @@ public class ApplicationCurrency extends AbstractPersistable {
@Column(name = "decimal_places", nullable = false)
private final Integer decimalPlaces;
+ @Column(name = "currency_multiplesof")
+ private final Integer inMultiplesOf;
+
@Column(name = "name", nullable = false, length = 50)
private final String name;
@@ -36,19 +39,22 @@ public class ApplicationCurrency extends AbstractPersistable {
this.code = null;
this.name = null;
this.decimalPlaces = null;
+ this.inMultiplesOf = null;
this.nameCode = null;
this.displaySymbol = null;
}
- public static ApplicationCurrency from(final ApplicationCurrency currency, final int decimalPlaces) {
- return new ApplicationCurrency(currency.code, currency.name, decimalPlaces, currency.nameCode, currency.displaySymbol);
+ public static ApplicationCurrency from(final ApplicationCurrency currency, final int decimalPlaces, final Integer inMultiplesOf) {
+ return new ApplicationCurrency(currency.code, currency.name, decimalPlaces, inMultiplesOf, currency.nameCode,
+ currency.displaySymbol);
}
- private ApplicationCurrency(final String code, final String name, final int decimalPlaces, final String nameCode,
- final String displaySymbol) {
+ private ApplicationCurrency(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
+ final String nameCode, final String displaySymbol) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
+ this.inMultiplesOf = inMultiplesOf;
this.nameCode = nameCode;
this.displaySymbol = displaySymbol;
}
@@ -65,6 +71,10 @@ public class ApplicationCurrency extends AbstractPersistable {
return this.decimalPlaces;
}
+ public Integer getCurrencyInMultiplesOf() {
+ return this.inMultiplesOf;
+ }
+
public String getNameCode() {
return nameCode;
}
@@ -74,14 +84,14 @@ public class ApplicationCurrency extends AbstractPersistable {
}
public CurrencyData toData() {
- return new CurrencyData(this.code, this.name, this.decimalPlaces, this.displaySymbol, this.nameCode);
+ return new CurrencyData(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.displaySymbol, this.nameCode);
}
- public CurrencyData toData(final int digitsAfterDecimalSupported) {
- return new CurrencyData(this.code, this.name, digitsAfterDecimalSupported, this.displaySymbol, this.nameCode);
+ public CurrencyData toData(final int digitsAfterDecimalSupported, final Integer inMultiplesOf) {
+ return new CurrencyData(this.code, this.name, digitsAfterDecimalSupported, inMultiplesOf, this.displaySymbol, this.nameCode);
}
public OrganisationCurrency toOrganisationCurrency() {
- return new OrganisationCurrency(this.code, this.name, this.decimalPlaces, this.nameCode, this.displaySymbol);
+ return new OrganisationCurrency(this.code, this.name, this.decimalPlaces,this.inMultiplesOf, this.nameCode, this.displaySymbol);
}
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java
index b5d8a94c0..009cf6ee9 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java
@@ -38,7 +38,7 @@ public class ApplicationCurrencyRepositoryWrapper {
if (defaultApplicationCurrency == null) { throw new CurrencyNotFoundException(currency.getCode()); }
final ApplicationCurrency applicationCurrency = ApplicationCurrency.from(defaultApplicationCurrency,
- currency.getDigitsAfterDecimal());
+ currency.getDigitsAfterDecimal(),currency.getCurrencyInMultiplesOf());
return applicationCurrency;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/MonetaryCurrency.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/MonetaryCurrency.java
index 46f5eb196..a34e45b46 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/MonetaryCurrency.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/MonetaryCurrency.java
@@ -17,18 +17,23 @@ public class MonetaryCurrency {
@Column(name = "currency_digits", nullable = false)
private final int digitsAfterDecimal;
+ @Column(name = "currency_multiplesof")
+ private final Integer inMultiplesOf;
+
protected MonetaryCurrency() {
this.code = null;
this.digitsAfterDecimal = 0;
+ this.inMultiplesOf = 0;
}
- public MonetaryCurrency(final String code, final int digitsAfterDecimal) {
+ public MonetaryCurrency(final String code, final int digitsAfterDecimal,final Integer inMultiplesOf) {
this.code = code;
this.digitsAfterDecimal = digitsAfterDecimal;
+ this.inMultiplesOf = inMultiplesOf;
}
public MonetaryCurrency copy() {
- return new MonetaryCurrency(this.code, this.digitsAfterDecimal);
+ return new MonetaryCurrency(this.code, this.digitsAfterDecimal,this.inMultiplesOf);
}
public String getCode() {
@@ -38,4 +43,8 @@ public class MonetaryCurrency {
public int getDigitsAfterDecimal() {
return digitsAfterDecimal;
}
+
+ public Integer getCurrencyInMultiplesOf() {
+ return inMultiplesOf;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/Money.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/Money.java
index 9b2f2402c..ac3be1fae 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/Money.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/domain/Money.java
@@ -21,6 +21,9 @@ public class Money implements Comparable {
@Column(name = "currency_digits")
private final int currencyDigitsAfterDecimal;
+ @Column(name = "currency_multiplesof")
+ private final Integer inMultiplesOf;
+
@Column(name = "amount", scale = 6, precision = 19)
private final BigDecimal amount;
@@ -44,46 +47,47 @@ public class Money implements Comparable {
}
public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) {
- return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount));
+ return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount),
+ currency.getCurrencyInMultiplesOf());
}
public static Money zero(final MonetaryCurrency currency) {
- return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO);
+ return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf());
}
protected Money() {
this.currencyCode = null;
this.currencyDigitsAfterDecimal = 0;
+ this.inMultiplesOf = 0;
this.amount = null;
}
- private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount) {
+ private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf) {
this.currencyCode = currencyCode;
this.currencyDigitsAfterDecimal = digitsAfterDecimal;
+ this.inMultiplesOf = inMultiplesOf;
final BigDecimal amountZeroed = defaultToZeroIfNull(amount);
final BigDecimal amountStripped = amountZeroed.stripTrailingZeros();
final BigDecimal amountScaled = amountStripped.setScale(this.currencyDigitsAfterDecimal, RoundingMode.HALF_EVEN);
- this.amount = amountScaled;
- // TODO - KW - below was test of using floor/ceil functions to support
- // round monetary amounts into multiples of say 20/50.
- // if (this.currencyDigitsAfterDecimal == 0) {
- // double existingVal = amountScaled.doubleValue();
- // double ceilingOfValue = ceiling(existingVal, Double.valueOf("20.0"));
- // double floorOfValue = floor(existingVal, Double.valueOf("20.0"));
- //
- // double floorDiff = existingVal - floorOfValue;
- // double ceilDiff = ceilingOfValue - existingVal;
- //
- // if (ceilDiff > floorDiff) {
- // this.amount = BigDecimal.valueOf(floorOfValue);
- // } else {
- // this.amount = BigDecimal.valueOf(ceilingOfValue);
- // }
- // } else {
- // this.amount = amountScaled;
- // }
+ // round monetary amounts into multiplesof say 20/50.
+ if (inMultiplesOf!= null && this.currencyDigitsAfterDecimal == 0 && inMultiplesOf>0 && amountScaled.doubleValue()>0) {
+ double existingVal = amountScaled.doubleValue();
+ double ceilingOfValue = ceiling(existingVal, inMultiplesOf);
+ double floorOfValue = floor(existingVal, inMultiplesOf);
+
+ double floorDiff = existingVal - floorOfValue;
+ double ceilDiff = ceilingOfValue - existingVal;
+
+ if (ceilDiff > floorDiff) {
+ this.amount = BigDecimal.valueOf(floorOfValue);
+ } else {
+ this.amount = BigDecimal.valueOf(ceilingOfValue);
+ }
+ } else {
+ this.amount = amountScaled;
+ }
}
public static double ceiling(final double n, final double s) {
@@ -119,7 +123,7 @@ public class Money implements Comparable {
}
public Money copy() {
- return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros());
+ return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros(), this.inMultiplesOf);
}
public Money plus(final Iterable extends Money> moniesToAdd) {
@@ -263,6 +267,10 @@ public class Money implements Comparable {
return this.currencyDigitsAfterDecimal;
}
+ public int getCurrencyInMultiplesOf() {
+ return this.inMultiplesOf;
+ }
+
public BigDecimal getAmount() {
return this.amount;
}
@@ -298,7 +306,7 @@ public class Money implements Comparable {
}
private MonetaryCurrency monetaryCurrency() {
- return new MonetaryCurrency(this.currencyCode, this.currencyDigitsAfterDecimal);
+ return new MonetaryCurrency(this.currencyCode, this.currencyDigitsAfterDecimal, this.inMultiplesOf);
}
public Money zero() {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java
index 661a58bc8..b594d8358 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java
@@ -58,14 +58,15 @@ public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformServ
final String code = rs.getString("code");
final String name = rs.getString("name");
final int decimalPlaces = JdbcSupport.getInteger(rs, "decimalPlaces");
+ final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final String displaySymbol = rs.getString("displaySymbol");
final String nameCode = rs.getString("nameCode");
- return new CurrencyData(code, name, decimalPlaces, displaySymbol, nameCode);
+ return new CurrencyData(code, name, decimalPlaces, inMultiplesOf, displaySymbol, nameCode);
}
public String schema() {
- return " c.code as code, c.name as name, c.decimal_places as decimalPlaces, c.display_symbol as displaySymbol, c.internationalized_name_code as nameCode ";
+ return " c.code as code, c.name as name, c.decimal_places as decimalPlaces,c.currency_multiplesof as inMultiplesOf, c.display_symbol as displaySymbol, c.internationalized_name_code as nameCode ";
}
}
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/api/OfficeTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/api/OfficeTransactionsApiResource.java
index 1fc5490e7..7bfffb2e6 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/api/OfficeTransactionsApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/api/OfficeTransactionsApiResource.java
@@ -41,7 +41,7 @@ import org.springframework.stereotype.Component;
public class OfficeTransactionsApiResource {
private static final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "transactionDate", "fromOfficeId",
- "fromOfficeName", "toOfficeId", "toOfficeIdName", "currencyCode", "digitsAfterDecimal", "transactionAmount", "description",
+ "fromOfficeName", "toOfficeId", "toOfficeIdName", "currencyCode", "digitsAfterDecimal","inMultiplesOf", "transactionAmount", "description",
"allowedOffices", "currencyOptions"));
private final String resourceNameForReadPermissions = "OFFICE";
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeRepositoryWrapper.java
new file mode 100644
index 000000000..7c660cb0c
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeRepositoryWrapper.java
@@ -0,0 +1,45 @@
+/**
+ * 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.organisation.office.domain;
+
+import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * Wrapper for {@link OfficeRepository} that adds NULL checking and Error
+ * handling capabilities
+ *
+ */
+@Service
+public class OfficeRepositoryWrapper {
+
+ private final OfficeRepository repository;
+
+ @Autowired
+ public OfficeRepositoryWrapper(final OfficeRepository repository) {
+ this.repository = repository;
+ }
+
+ public Office findOneWithNotFoundDetection(final Long id) {
+ final Office office = this.repository.findOne(id);
+ if (office == null) { throw new OfficeNotFoundException(id); }
+ return office;
+ }
+
+ public void save(final Office entity) {
+ this.repository.save(entity);
+ }
+
+ public void saveAndFlush(final Office entity) {
+ this.repository.saveAndFlush(entity);
+ }
+
+ public void delete(final Office entity) {
+ this.repository.delete(entity);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeTransaction.java
index b4cf3be75..8ce899019 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeTransaction.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeTransaction.java
@@ -28,30 +28,24 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_office_transaction")
public class OfficeTransaction extends AbstractPersistable {
- @SuppressWarnings("unused")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "from_office_id")
private Office from;
- @SuppressWarnings("unused")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "to_office_id")
private Office to;
- @SuppressWarnings("unused")
@Column(name = "transaction_date", nullable = false)
@Temporal(TemporalType.DATE)
private Date transactionDate;
- @SuppressWarnings("unused")
@Embedded
private MonetaryCurrency currency;
- @SuppressWarnings("unused")
@Column(name = "transaction_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal transactionAmount;
- @SuppressWarnings("unused")
@Column(name = "description", nullable = true, length = 100)
private String description;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OrganisationCurrency.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OrganisationCurrency.java
index c0a88dc58..f763da754 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OrganisationCurrency.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OrganisationCurrency.java
@@ -18,23 +18,21 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_organisation_currency")
public class OrganisationCurrency extends AbstractPersistable {
- @SuppressWarnings("unused")
@Column(name = "code", nullable = false, length = 3)
private final String code;
- @SuppressWarnings("unused")
@Column(name = "decimal_places", nullable = false)
private final Integer decimalPlaces;
- @SuppressWarnings("unused")
+ @Column(name = "currency_multiplesof")
+ private final Integer inMultiplesOf;
+
@Column(name = "name", nullable = false, length = 50)
private final String name;
- @SuppressWarnings("unused")
@Column(name = "internationalized_name_code", nullable = false, length = 50)
private final String nameCode;
- @SuppressWarnings("unused")
@Column(name = "display_symbol", nullable = true, length = 10)
private final String displaySymbol;
@@ -42,15 +40,17 @@ public class OrganisationCurrency extends AbstractPersistable {
this.code = null;
this.name = null;
this.decimalPlaces = null;
+ this.inMultiplesOf = null;
this.nameCode = null;
this.displaySymbol = null;
}
- public OrganisationCurrency(final String code, final String name, final int decimalPlaces, final String nameCode,
- final String displaySymbol) {
+ public OrganisationCurrency(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
+ final String nameCode, final String displaySymbol) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
+ this.inMultiplesOf = inMultiplesOf;
this.nameCode = nameCode;
this.displaySymbol = displaySymbol;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformServiceImpl.java
index 564da7eee..c0675c4e4 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformServiceImpl.java
@@ -90,7 +90,7 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
public String schema() {
return " ot.id as id, ot.transaction_date as transactionDate, ot.from_office_id as fromOfficeId, fromoff.name as fromOfficeName, "
+ " ot.to_office_id as toOfficeId, tooff.name as toOfficeName, ot.transaction_amount as transactionAmount, ot.description as description, "
- + " ot.currency_code as currencyCode, rc.decimal_places as currencyDigits, "
+ + " ot.currency_code as currencyCode, rc.decimal_places as currencyDigits, rc.currency_multiplesof as inMultiplesOf, "
+ " rc.name as currencyName, rc.internationalized_name_code as currencyNameCode, rc.display_symbol as currencyDisplaySymbol "
+ " from m_office_transaction ot "
+ " left join m_office fromoff on fromoff.id = ot.from_office_id "
@@ -112,9 +112,10 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
String currencyNameCode = rs.getString("currencyNameCode");
String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
+ Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
- CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol,
- currencyNameCode);
+ CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
+ currencyDisplaySymbol, currencyNameCode);
BigDecimal transactionAmount = rs.getBigDecimal("transactionAmount");
String description = rs.getString("description");
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java
index e926ea009..111ebcc86 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java
@@ -159,7 +159,7 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP
final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
final ApplicationCurrency appCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currencyCode);
- final MonetaryCurrency currency = new MonetaryCurrency(appCurrency.getCode(), appCurrency.getDecimalPlaces());
+ final MonetaryCurrency currency = new MonetaryCurrency(appCurrency.getCode(), appCurrency.getDecimalPlaces(),appCurrency.getCurrencyInMultiplesOf());
final Money amount = Money.of(currency, command.bigDecimalValueOfParameterNamed("transactionAmount"));
final OfficeTransaction entity = OfficeTransaction.fromJson(fromOffice, toOffice, amount, command);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/api/StaffApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/api/StaffApiResource.java
index 22009a5e7..fe20fe593 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/api/StaffApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/api/StaffApiResource.java
@@ -48,7 +48,7 @@ public class StaffApiResource {
* {@link StaffData}.
*/
private final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "firstname", "lastname", "displayName",
- "officeId", "officeName", "loanOfficerFlag", "allowedOffices"));
+ "officeId", "officeName", "loanOfficerFlag", "externalId", "allowedOffices"));
private final String resourceNameForPermissions = "STAFF";
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/data/StaffAccountSummaryCollectionData.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/data/StaffAccountSummaryCollectionData.java
index bb4fbb40d..0fd820705 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/data/StaffAccountSummaryCollectionData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/data/StaffAccountSummaryCollectionData.java
@@ -8,8 +8,7 @@ package org.mifosplatform.organisation.staff.data;
import java.util.Collection;
import java.util.List;
-import org.mifosplatform.portfolio.client.data.ClientLoanAccountSummaryData;
-import org.mifosplatform.portfolio.group.data.GroupAccountSummaryData;
+import org.mifosplatform.portfolio.accountdetails.data.LoanAccountSummaryData;
/**
*
@@ -17,23 +16,23 @@ import org.mifosplatform.portfolio.group.data.GroupAccountSummaryData;
public class StaffAccountSummaryCollectionData {
@SuppressWarnings("unused")
- private final List clients;
+ private final List clients;
@SuppressWarnings("unused")
- private final List groups;
+ private final List groups;
- public StaffAccountSummaryCollectionData(final List clients, final List groups) {
+ public StaffAccountSummaryCollectionData(final List clients, final List groups) {
this.clients = clients;
this.groups = groups;
}
- public static final class ClientSummary {
+ public static final class LoanAccountSummary {
private final Long id;
private final String displayName;
- private Collection loans;
+ private Collection loans;
- public ClientSummary(Long id, String displayName) {
+ public LoanAccountSummary(Long id, String displayName) {
this.id = id;
this.displayName = displayName;
}
@@ -46,41 +45,13 @@ public class StaffAccountSummaryCollectionData {
return displayName;
}
- public Collection getLoans() {
+ public Collection getLoans() {
return loans;
}
- public void setLoans(Collection loans) {
+ public void setLoans(Collection loans) {
this.loans = loans;
}
}
- public static final class GroupSummary {
-
- private final Long id;
- private final String name;
-
- private Collection loans;
-
- public GroupSummary(Long id, String name) {
- this.id = id;
- this.name = name;
- }
-
- public Long getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
-
- public Collection getLoans() {
- return loans;
- }
-
- public void setLoans(Collection loans) {
- this.loans = loans;
- }
- }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/data/StaffData.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/data/StaffData.java
index d2e0557d8..2356341e4 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/data/StaffData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/data/StaffData.java
@@ -21,32 +21,34 @@ public class StaffData {
private final Long officeId;
private final String officeName;
private final Boolean isLoanOfficer;
+ private final String externalId;
@SuppressWarnings("unused")
private final Collection allowedOffices;
public static StaffData templateData(final StaffData staff, final Collection allowedOffices) {
return new StaffData(staff.id, staff.firstname, staff.lastname, staff.displayName, staff.officeId, staff.officeName,
- staff.isLoanOfficer, allowedOffices);
+ staff.isLoanOfficer, staff.externalId, allowedOffices);
}
public static StaffData lookup(final Long id, final String displayName) {
- return new StaffData(id, null, null, displayName, null, null, null, null);
+ return new StaffData(id, null, null, displayName, null, null, null, null, null);
}
public static StaffData instance(final Long id, final String firstname, final String lastname, final String displayName,
- final Long officeId, final String officeName, final Boolean isLoanOfficer) {
- return new StaffData(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, null);
+ final Long officeId, final String officeName, final Boolean isLoanOfficer, final String externalId) {
+ return new StaffData(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, null);
}
private StaffData(final Long id, final String firstname, final String lastname, final String displayName, final Long officeId,
- final String officeName, final Boolean isLoanOfficer, final Collection allowedOffices) {
+ final String officeName, final Boolean isLoanOfficer, final String externalId, final Collection allowedOffices) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.displayName = displayName;
this.officeName = officeName;
this.isLoanOfficer = isLoanOfficer;
+ this.externalId = externalId;
this.officeId = officeId;
this.allowedOffices = allowedOffices;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/domain/Staff.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/domain/Staff.java
index 22f7b2879..d4c9d7b36 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/domain/Staff.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/domain/Staff.java
@@ -22,7 +22,8 @@ import org.mifosplatform.organisation.office.domain.Office;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
-@Table(name = "m_staff", uniqueConstraints = { @UniqueConstraint(columnNames = { "display_name" }, name = "display_name") })
+@Table(name = "m_staff", uniqueConstraints = { @UniqueConstraint(columnNames = { "display_name" }, name = "display_name") ,
+@UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") })
public class Staff extends AbstractPersistable {
@Column(name = "firstname", length = 50)
@@ -34,6 +35,9 @@ public class Staff extends AbstractPersistable {
@Column(name = "display_name", length = 100)
private String displayName;
+ @Column(name = "external_id", length = 100, nullable = true, unique = true)
+ private String externalId;
+
// Office to which this employee belongs
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
@@ -46,7 +50,6 @@ public class Staff extends AbstractPersistable {
@Column(name = "organisational_role_enum", nullable = true)
private Integer organisationalRoleType;
- @SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "organisational_role_parent_staff_id", nullable = true)
private Staff organisationalRoleParentStaff;
@@ -54,29 +57,33 @@ public class Staff extends AbstractPersistable {
public static Staff fromJson(final Office staffOffice, final JsonCommand command) {
final String firstnameParamName = "firstname";
- String firstname = command.stringValueOfParameterNamed(firstnameParamName);
+ final String firstname = command.stringValueOfParameterNamed(firstnameParamName);
final String lastnameParamName = "lastname";
- String lastname = command.stringValueOfParameterNamed(lastnameParamName);
+ final String lastname = command.stringValueOfParameterNamed(lastnameParamName);
+
+ final String externalIdParamName = "externalId";
+ final String externalId = command.stringValueOfParameterNamedAllowingNull(externalIdParamName);
final String isLoanOfficerParamName = "isLoanOfficer";
final boolean isLoanOfficer = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
- return new Staff(staffOffice, firstname, lastname, isLoanOfficer);
+ return new Staff(staffOffice, firstname, lastname, externalId, isLoanOfficer);
}
- public static Staff createNew(final Office staffOffice, final String firstname, final String lastname, final boolean isLoanOfficer) {
- return new Staff(staffOffice, firstname, lastname, isLoanOfficer);
+ public static Staff createNew(final Office staffOffice, final String firstname, final String lastname, final String externalId, final boolean isLoanOfficer) {
+ return new Staff(staffOffice, firstname, lastname, externalId, isLoanOfficer);
}
protected Staff() {
//
}
- private Staff(final Office staffOffice, final String firstname, final String lastname, final boolean isLoanOfficer) {
+ private Staff(final Office staffOffice, final String firstname, final String lastname, final String externalId, final boolean isLoanOfficer) {
this.office = staffOffice;
this.firstname = StringUtils.defaultIfEmpty(firstname, null);
this.lastname = StringUtils.defaultIfEmpty(lastname, null);
+ this.externalId = externalId;
this.loanOfficer = isLoanOfficer;
deriveDisplayName(firstname);
}
@@ -125,6 +132,13 @@ public class Staff extends AbstractPersistable {
deriveDisplayName(this.firstname);
}
+ final String externalIdParamName = "externalId";
+ if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
+ final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
+ actualChanges.put(externalIdParamName, newValue);
+ this.externalId = newValue;
+ }
+
final String isLoanOfficerParamName = "isLoanOfficer";
if (command.isChangeInBooleanParameterNamed(isLoanOfficerParamName, this.loanOfficer)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/serialization/StaffCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/serialization/StaffCommandFromApiJsonDeserializer.java
index c2cd8c018..c1562e58a 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/serialization/StaffCommandFromApiJsonDeserializer.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/serialization/StaffCommandFromApiJsonDeserializer.java
@@ -31,7 +31,7 @@ public final class StaffCommandFromApiJsonDeserializer {
/**
* The parameters supported for this command.
*/
- private final Set supportedParameters = new HashSet(Arrays.asList("firstname", "lastname", "officeId", "isLoanOfficer"));
+ private final Set supportedParameters = new HashSet(Arrays.asList("firstname", "lastname", "officeId", "externalId", "isLoanOfficer"));
private final FromJsonHelper fromApiJsonHelper;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformServiceImpl.java
index cbd0d1791..52422e9b0 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformServiceImpl.java
@@ -38,7 +38,7 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
public String schema() {
return " s.id as id,s.office_id as officeId, o.name as officeName, s.firstname as firstname, s.lastname as lastname,"
- + " s.display_name as displayName, s.is_loan_officer as isLoanOfficer from m_staff s "
+ + " s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId from m_staff s "
+ " join m_office o on o.id = s.office_id";
}
@@ -52,8 +52,9 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
final Long officeId = rs.getLong("officeId");
final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer");
final String officeName = rs.getString("officeName");
+ final String externalId = rs.getString("externalId");
- return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer);
+ return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId);
}
}
@@ -64,7 +65,7 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("s.id as id, s.office_id as officeId, o.name as officeName,");
sqlBuilder.append("s.firstname as firstname, s.lastname as lastname,");
- sqlBuilder.append("s.display_name as displayName, s.is_loan_officer as isLoanOfficer ");
+ sqlBuilder.append("s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId ");
sqlBuilder.append("from m_office o ");
sqlBuilder.append("join m_office ohierarchy on o.hierarchy like concat(ohierarchy.hierarchy, '%') ");
sqlBuilder.append("join m_staff s on s.office_id = ohierarchy.id ");
@@ -88,8 +89,9 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
final Long officeId = rs.getLong("officeId");
final String officeName = rs.getString("officeName");
final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer");
+ final String externalId = rs.getString("externalId");
- return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer);
+ return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId);
}
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java
index 27f960e52..ad66f77c6 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffWritePlatformServiceJpaRepositoryImpl.java
@@ -98,7 +98,7 @@ public class StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePla
}
if (!changesOnly.isEmpty()) {
- this.staffRepository.save(staffForUpdate);
+ this.staffRepository.saveAndFlush(staffForUpdate);
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(staffId)
@@ -115,7 +115,14 @@ public class StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePla
*/
private void handleStaffDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
Throwable realCause = dve.getMostSpecificCause();
- if (realCause.getMessage().contains("display_name")) {
+
+ if (realCause.getMessage().contains("external_id")) {
+
+ final String externalId = command.stringValueOfParameterNamed("externalId");
+ throw new PlatformDataIntegrityException("error.msg.staff.duplicate.externalId", "Staff with externalId `" + externalId
+ + "` already exists", "externalId", externalId);
+ }
+ else if (realCause.getMessage().contains("display_name")) {
final String lastname = command.stringValueOfParameterNamed("lastname");
String displayName = lastname;
if (!StringUtils.isBlank(displayName)) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java
index f17d12d21..dc81b8224 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java
@@ -29,10 +29,8 @@ public class WorkingDaysRepositoryWrapper {
public WorkingDays findOne() {
final List workingDaysList = this.repository.findAll();
-
- if (workingDaysList == null || workingDaysList.isEmpty()) {
- throw new WorkingDaysNotFoundException();
- }
+
+ if (workingDaysList == null || workingDaysList.isEmpty()) { throw new WorkingDaysNotFoundException(); }
return workingDaysList.get(0);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java
index 56a4a5b02..4fb1dc9f0 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java
@@ -9,7 +9,7 @@ package org.mifosplatform.organisation.workingdays.service;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.workingdays.domain.RepaymentRescheduleType;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
-import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
+import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
public class WorkingDaysUtil {
@@ -38,6 +38,6 @@ public class WorkingDaysUtil {
}
public static boolean isWorkingDay(WorkingDays workingDays, final LocalDate date){
- return CalendarHelper.isValidRedurringDate(workingDays.getRecurrence(), date, date);
+ return CalendarUtils.isValidRedurringDate(workingDays.getRecurrence(), date, date);
}
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/AccountTransfersApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/AccountTransfersApiConstants.java
new file mode 100644
index 000000000..a178fd708
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/AccountTransfersApiConstants.java
@@ -0,0 +1,50 @@
+/**
+ * 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.account;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mifosplatform.portfolio.account.data.AccountTransferData;
+
+public class AccountTransfersApiConstants {
+
+ public static final String ACCOUNT_TRANSFER_RESOURCE_NAME = "accounttransfer";
+
+ // general
+ public static final String localeParamName = "locale";
+ public static final String dateFormatParamName = "dateFormat";
+
+ // savings product and account parameters
+ public static final String idParamName = "id";
+ public static final String fromOfficeIdParamName = "fromOfficeId";
+ public static final String fromClientIdParamName = "fromClientId";
+ public static final String fromAccountIdParamName = "fromAccountId";
+ public static final String fromAccountTypeParamName = "fromAccountType";
+ public static final String toOfficeIdParamName = "toOfficeId";
+ public static final String toClientIdParamName = "toClientId";
+ public static final String toAccountIdParamName = "toAccountId";
+ public static final String toAccountTypeParamName = "toAccountType";
+
+ // transaction parameters
+ public static final String transferDateParamName = "transferDate";
+ public static final String transferAmountParamName = "transferAmount";
+ public static final String transferDescriptionParamName = "transferDescription";
+
+ public static final Set REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName,
+ fromOfficeIdParamName, fromClientIdParamName, fromAccountTypeParamName, fromAccountIdParamName, toOfficeIdParamName,
+ toClientIdParamName, toAccountTypeParamName, toAccountIdParamName, transferDateParamName, transferAmountParamName,
+ transferDescriptionParamName));
+
+ /**
+ * These parameters will match the class level parameters of
+ * {@link AccountTransferData}. Where possible, we try to get response
+ * parameters to match those of request parameters.
+ */
+ public static final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, transferDescriptionParamName,
+ "currency"));
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/PortfolioAccountType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/PortfolioAccountType.java
new file mode 100644
index 000000000..ad65f8bfb
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/PortfolioAccountType.java
@@ -0,0 +1,67 @@
+/**
+ * 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.account;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum PortfolioAccountType {
+
+ INVALID(0, "accountType.invalid"), //
+ LOAN(1, "accountType.loan"), //
+ SAVINGS(2, "accountType.savings");
+
+ private final Integer value;
+ private final String code;
+
+ private PortfolioAccountType(final Integer value, final String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public Integer getValue() {
+ return this.value;
+ }
+
+ public String getCode() {
+ return this.code;
+ }
+
+ public static Object[] integerValues() {
+ final List values = new ArrayList();
+ for (final PortfolioAccountType enumType : values()) {
+ if (enumType.getValue() > 0) {
+ values.add(enumType.getValue());
+ }
+ }
+
+ return values.toArray();
+ }
+
+ public static PortfolioAccountType fromInt(final Integer type) {
+
+ PortfolioAccountType enumType = PortfolioAccountType.INVALID;
+ if (type != null) {
+ switch (type) {
+ case 1:
+ enumType = LOAN;
+ break;
+ case 2:
+ enumType = SAVINGS;
+ break;
+ }
+ }
+ return enumType;
+ }
+
+ public boolean isSavingsAccount() {
+ return this.value == Integer.valueOf(2);
+ }
+
+ public boolean isLoanAccount() {
+ return this.value == Integer.valueOf(1);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/api/AccountTransfersApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/api/AccountTransfersApiResource.java
new file mode 100644
index 000000000..01f551d83
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/api/AccountTransfersApiResource.java
@@ -0,0 +1,122 @@
+/**
+ * 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.account.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+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.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.serialization.ApiRequestJsonSerializationSettings;
+import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.mifosplatform.infrastructure.core.service.Page;
+import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
+import org.mifosplatform.portfolio.account.AccountTransfersApiConstants;
+import org.mifosplatform.portfolio.account.data.AccountTransferData;
+import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService;
+import org.mifosplatform.portfolio.group.service.SearchParameters;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/accounttransfers")
+@Component
+@Scope("singleton")
+public class AccountTransfersApiResource {
+
+ private final PlatformSecurityContext context;
+ private final DefaultToApiJsonSerializer toApiJsonSerializer;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
+
+ @Autowired
+ public AccountTransfersApiResource(final PlatformSecurityContext context,
+ final DefaultToApiJsonSerializer toApiJsonSerializer,
+ final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+ final ApiRequestParameterHelper apiRequestParameterHelper,
+ final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
+ this.context = context;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
+ }
+
+ @GET
+ @Path("template")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String template(@QueryParam("fromOfficeId") final Long fromOfficeId, @QueryParam("fromClientId") final Long fromClientId,
+ @QueryParam("fromAccountId") final Long fromAccountId, @QueryParam("fromAccountType") final Integer fromAccountType,
+ @QueryParam("toOfficeId") final Long toOfficeId, @QueryParam("toClientId") final Long toClientId,
+ @QueryParam("toAccountId") final Long toAccountId, @QueryParam("toAccountType") final Integer toAccountType,
+ @Context final UriInfo uriInfo) {
+
+ context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+ AccountTransferData transferData = this.accountTransfersReadPlatformService.retrieveTemplate(fromOfficeId, fromClientId,
+ fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType);
+
+ final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, transferData, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+ }
+
+ @POST
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String create(final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().createAccountTransfer().withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
+ @QueryParam("externalId") final String externalId, @QueryParam("offset") final Integer offset,
+ @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+ @QueryParam("sortOrder") final String sortOrder) {
+
+ context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+ final SearchParameters searchParameters = SearchParameters.forLoans(sqlSearch, externalId, offset, limit, orderBy, sortOrder);
+
+ final Page transfers = this.accountTransfersReadPlatformService.retrieveAll(searchParameters);
+
+ final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, transfers, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+ }
+
+ @GET
+ @Path("{transferId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveOne(@PathParam("transferId") final Long transferId, @Context final UriInfo uriInfo) {
+
+ context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
+
+ AccountTransferData transfer = this.accountTransfersReadPlatformService.retrieveOne(transferId);
+
+ final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, transfer, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransferData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransferData.java
new file mode 100644
index 000000000..eeeb11d7e
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransferData.java
@@ -0,0 +1,122 @@
+/**
+ * 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.account.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.organisation.office.data.OfficeData;
+import org.mifosplatform.portfolio.client.data.ClientData;
+
+/**
+ * Immutable data object representing a savings account.
+ */
+@SuppressWarnings("unused")
+public class AccountTransferData {
+
+ private final Long id;
+ private final Boolean reversed;
+ private final CurrencyData currency;
+ private final BigDecimal transferAmount;
+ private final LocalDate transferDate;
+ private final String transferDescription;
+ private final OfficeData fromOffice;
+ private final ClientData fromClient;
+ private final EnumOptionData fromAccountType;
+ private final PortfolioAccountData fromAccount;
+ private final OfficeData toOffice;
+ private final ClientData toClient;
+ private final EnumOptionData toAccountType;
+ private final PortfolioAccountData toAccount;
+
+ // template
+ private final Collection fromOfficeOptions;
+ private final Collection fromClientOptions;
+ private final Collection fromAccountTypeOptions;
+ private final Collection fromAccountOptions;
+ private final Collection toOfficeOptions;
+ private final Collection toClientOptions;
+ private final Collection toAccountTypeOptions;
+ private final Collection toAccountOptions;
+
+ public static AccountTransferData template(final OfficeData fromOffice, final ClientData fromClient,
+ final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final LocalDate transferDate,
+ final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
+ final Collection fromOfficeOptions, final Collection fromClientOptions,
+ final Collection fromAccountTypeOptions, final Collection fromAccountOptions,
+ final Collection toOfficeOptions, final Collection toClientOptions,
+ final Collection toAccountTypeOptions, final Collection toAccountOptions) {
+ final Long id = null;
+ CurrencyData currency = null;
+ if (fromAccount != null) {
+ currency = fromAccount.currency();
+ }
+
+ final BigDecimal transferAmount = BigDecimal.ZERO;
+ final String transferDescription = null;
+ final Boolean reversed = null;
+ return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccountType, fromAccount, currency, transferAmount,
+ transferDate, transferDescription, toOffice, toClient, toAccountType, toAccount, fromOfficeOptions, fromClientOptions,
+ fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
+ }
+
+ public static AccountTransferData instance(final Long id, final Boolean reversed, final LocalDate transferDate,
+ final CurrencyData currency, final BigDecimal transferAmount, final String transferDescription, final OfficeData fromOffice,
+ final OfficeData toOffice, final ClientData fromClient, final ClientData toClient, final EnumOptionData fromAccountType,
+ final PortfolioAccountData fromAccount, final EnumOptionData toAccountType, final PortfolioAccountData toAccount) {
+
+ return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccountType, fromAccount, currency, transferAmount,
+ transferDate, transferDescription, toOffice, toClient, toAccountType, toAccount, null, null, null, null, null, null,
+ null, null);
+ }
+
+ public static AccountTransferData transferBasicDetails(final Long id, final CurrencyData currency, final BigDecimal transferAmount,
+ final LocalDate transferDate, final String description, final Boolean reversed) {
+
+ final EnumOptionData fromAccountType = null;
+ final EnumOptionData toAccountType = null;
+
+ return new AccountTransferData(id, reversed, null, null, fromAccountType, null, currency, transferAmount, transferDate,
+ description, null, null, toAccountType, null, null, null, null, null, null, null, null, null);
+ }
+
+ private AccountTransferData(final Long id, final Boolean reversed, final OfficeData fromOffice, final ClientData fromClient,
+ final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final CurrencyData currency,
+ final BigDecimal transferAmount, final LocalDate transferDate, final String transferDescription, final OfficeData toOffice,
+ final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
+ final Collection fromOfficeOptions, final Collection fromClientOptions,
+ final Collection fromAccountTypeOptions, final Collection fromAccountOptions,
+ final Collection toOfficeOptions, final Collection toClientOptions,
+ final Collection toAccountTypeOptions, final Collection toAccountOptions) {
+ this.id = id;
+ this.reversed = reversed;
+ this.fromOffice = fromOffice;
+ this.fromClient = fromClient;
+ this.fromAccountType = fromAccountType;
+ this.fromAccount = fromAccount;
+ this.toOffice = toOffice;
+ this.toClient = toClient;
+ this.toAccountType = toAccountType;
+ this.toAccount = toAccount;
+
+ this.currency = currency;
+ this.transferAmount = transferAmount;
+ this.transferDate = transferDate;
+ this.transferDescription = transferDescription;
+
+ this.fromOfficeOptions = fromOfficeOptions;
+ this.fromClientOptions = fromClientOptions;
+ this.fromAccountTypeOptions = fromAccountTypeOptions;
+ this.fromAccountOptions = fromAccountOptions;
+ this.toOfficeOptions = toOfficeOptions;
+ this.toClientOptions = toClientOptions;
+ this.toAccountTypeOptions = toAccountTypeOptions;
+ this.toAccountOptions = toAccountOptions;
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransfersDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransfersDataValidator.java
new file mode 100644
index 000000000..1d49518d8
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransfersDataValidator.java
@@ -0,0 +1,108 @@
+/**
+ * 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.account.data;
+
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.REQUEST_DATA_PARAMETERS;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountIdParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountTypeParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromClientIdParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromOfficeIdParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountIdParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountTypeParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toClientIdParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toOfficeIdParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferAmountParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferDateParamName;
+import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferDescriptionParamName;
+
+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.joda.time.LocalDate;
+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.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 AccountTransfersDataValidator {
+
+ private final FromJsonHelper fromApiJsonHelper;
+
+ @Autowired
+ public AccountTransfersDataValidator(final FromJsonHelper fromApiJsonHelper) {
+ this.fromApiJsonHelper = fromApiJsonHelper;
+ }
+
+ public void validate(final JsonCommand command) {
+
+ final String json = command.json();
+
+ if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+ final Type typeOfMap = new TypeToken