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

Overview

@@ -208,6 +208,14 @@ + + clients/{clientId}?command=transfer + Transfer Client + + + + + clients/{clientId}/accounts @@ -567,65 +575,27 @@ - -
-
-
- - -

Accounting

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

+

+

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

+

+

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 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 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>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(ACCOUNT_TRANSFER_RESOURCE_NAME); + + final JsonElement element = command.parsedJson(); + + final Long fromOfficeId = fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element); + baseDataValidator.reset().parameter(fromOfficeIdParamName).value(fromOfficeId).notNull().integerGreaterThanZero(); + + final Long fromClientId = fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element); + baseDataValidator.reset().parameter(fromClientIdParamName).value(fromClientId).notNull().integerGreaterThanZero(); + + final Long fromAccountId = fromApiJsonHelper.extractLongNamed(fromAccountIdParamName, element); + baseDataValidator.reset().parameter(fromAccountIdParamName).value(fromAccountId).notNull().integerGreaterThanZero(); + + final Integer fromAccountType = fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element); + baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType) + .isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2)); + + final Long toOfficeId = fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element); + baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero(); + + final Long toClientId = fromApiJsonHelper.extractLongNamed(toClientIdParamName, element); + baseDataValidator.reset().parameter(toClientIdParamName).value(toClientId).notNull().integerGreaterThanZero(); + + final Long toAccountId = fromApiJsonHelper.extractLongNamed(toAccountIdParamName, element); + baseDataValidator.reset().parameter(toAccountIdParamName).value(toAccountId).notNull().integerGreaterThanZero(); + + final Integer toAccountType = fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element); + baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType) + .isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2)); + + final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed(transferDateParamName, element); + baseDataValidator.reset().parameter(transferDateParamName).value(transactionDate).notNull(); + + final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transferAmountParamName, element); + baseDataValidator.reset().parameter(transferAmountParamName).value(transactionAmount).notNull().positiveAmount(); + + final String transactionDescription = fromApiJsonHelper.extractStringNamed(transferDescriptionParamName, element); + baseDataValidator.reset().parameter(transferDescriptionParamName).value(transactionDescription).notBlank().notExceedingLengthOf(200); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/PortfolioAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/PortfolioAccountData.java new file mode 100644 index 000000000..f34ea35ea --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/PortfolioAccountData.java @@ -0,0 +1,78 @@ +/** + * 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 org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.mifosplatform.organisation.monetary.data.CurrencyData; + +/** + * Immutable data object representing a savings account. + */ +@SuppressWarnings("unused") +public class PortfolioAccountData { + + private final Long id; + private final String accountNo; + private final String externalId; + private final Long groupId; + private final String groupName; + private final Long clientId; + private final String clientName; + private final Long productId; + private final String productName; + private final Long fieldOfficerId; + private final String fieldOfficerName; + private final CurrencyData currency; + + public static PortfolioAccountData lookup(final Long accountId, final String accountNo) { + return new PortfolioAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null); + } + + public PortfolioAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName, + final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldofficerId, + final String fieldofficerName, final CurrencyData currency) { + this.id = id; + this.accountNo = accountNo; + this.externalId = externalId; + this.groupId = groupId; + this.groupName = groupName; + this.clientId = clientId; + this.clientName = clientName; + this.productId = productId; + this.productName = productName; + this.fieldOfficerId = fieldofficerId; + this.fieldOfficerName = fieldofficerName; + this.currency = currency; + } + + @Override + public boolean equals(final Object obj) { + + if (obj == null) { return false; } + if (obj == this) { return true; } + if (obj.getClass() != getClass()) { return false; } + final PortfolioAccountData rhs = (PortfolioAccountData) obj; + return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).append(this.productId, rhs.productId).append(this.productName, rhs.productName).isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).append(this.productId).append(this.productName).toHashCode(); + } + + public Long clientId() { + return this.clientId; + } + + public CurrencyData currency() { + return this.currency; + } + + public String currencyCode() { + return this.currency.code(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransfer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransfer.java new file mode 100644 index 000000000..6a52ee403 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransfer.java @@ -0,0 +1,132 @@ +/** + * 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.domain; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.joda.time.LocalDate; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.organisation.office.domain.Office; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.savings.domain.SavingsAccount; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_savings_account_transfer") +public class AccountTransfer extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "from_office_id", nullable = false) + private Office fromOffice; + + @ManyToOne + @JoinColumn(name = "from_client_id", nullable = false) + private Client fromClient; + + @ManyToOne + @JoinColumn(name = "from_savings_account_id", nullable = true) + private SavingsAccount fromAccount; + + @ManyToOne + @JoinColumn(name = "to_office_id", nullable = false) + private Office toOffice; + + @ManyToOne + @JoinColumn(name = "to_client_id", nullable = false) + private Client toClient; + + @ManyToOne + @JoinColumn(name = "to_savings_account_id", nullable = true) + private SavingsAccount toSavingsAccount; + + @ManyToOne + @JoinColumn(name = "from_savings_transaction_id", nullable = true) + private SavingsAccountTransaction fromTransaction; + + @ManyToOne + @JoinColumn(name = "to_savings_transaction_id", nullable = true) + private SavingsAccountTransaction toSavingsTransaction; + + @ManyToOne + @JoinColumn(name = "to_loan_account_id", nullable = true) + private Loan toLoanAccount; + + @ManyToOne + @JoinColumn(name = "to_loan_transaction_id", nullable = true) + private LoanTransaction toLoanTransaction; + + @Column(name = "is_reversed", nullable = false) + private final boolean reversed = false; + + @Temporal(TemporalType.DATE) + @Column(name = "transaction_date") + private Date date; + + @Embedded + private MonetaryCurrency currency; + + @Column(name = "amount", scale = 6, precision = 19, nullable = false) + private BigDecimal amount; + + @Column(name = "description", length = 100) + private String description; + + public static AccountTransfer savingsToSavingsTransfer(final Office fromOffice, final Client fromClient, + final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, + final SavingsAccountTransaction withdrawal, final SavingsAccountTransaction deposit, final LocalDate transactionDate, + final Money transactionAmount, final String description) { + + return new AccountTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toSavingsAccount, null, withdrawal, + deposit, null, transactionDate, transactionAmount, description); + } + + public static AccountTransfer savingsToLoanTransfer(final Office fromOffice, final Client fromClient, + final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount, + final SavingsAccountTransaction withdrawal, final LoanTransaction loanRepaymentTransaction, final LocalDate transactionDate, + final Money transactionAmount, final String description) { + return new AccountTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, null, toLoanAccount, withdrawal, null, + loanRepaymentTransaction, transactionDate, transactionAmount, description); + } + + protected AccountTransfer() { + // + } + + private AccountTransfer(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount, + final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, final Loan toLoanAccount, + final SavingsAccountTransaction withdrawal, final SavingsAccountTransaction deposit, + final LoanTransaction loanRepaymentTransaction, final LocalDate transactionDate, final Money transactionAmount, + final String description) { + this.fromOffice = fromOffice; + this.fromClient = fromClient; + this.fromAccount = fromSavingsAccount; + this.toOffice = toOffice; + this.toClient = toClient; + this.toSavingsAccount = toSavingsAccount; + this.fromTransaction = withdrawal; + this.toSavingsTransaction = deposit; + this.toLoanAccount = toLoanAccount; + this.toLoanTransaction = loanRepaymentTransaction; + this.date = transactionDate.toDate(); + this.currency = transactionAmount.getCurrency(); + this.amount = transactionAmount.getAmountDefaultedToNullIfZero(); + this.description = description; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferAssembler.java new file mode 100644 index 000000000..ffdadd61e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferAssembler.java @@ -0,0 +1,114 @@ +/** + * 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.domain; + +import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountIdParamName; +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.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.math.BigDecimal; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.organisation.office.domain.Office; +import org.mifosplatform.organisation.office.domain.OfficeRepository; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; +import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.savings.domain.SavingsAccount; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.gson.JsonElement; + +@Service +public class AccountTransferAssembler { + + private final ClientRepositoryWrapper clientRepository; + private final OfficeRepository officeRepository; + private final SavingsAccountAssembler savingsAccountAssembler; + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public AccountTransferAssembler(final ClientRepositoryWrapper clientRepository, final OfficeRepository officeRepository, + final SavingsAccountAssembler savingsAccountAssembler, final FromJsonHelper fromApiJsonHelper) { + this.clientRepository = clientRepository; + this.officeRepository = officeRepository; + this.savingsAccountAssembler = savingsAccountAssembler; + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public AccountTransfer assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccountTransaction withdrawal, + final SavingsAccountTransaction deposit) { + + final JsonElement element = command.parsedJson(); + + final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element); + final Office fromOffice = this.officeRepository.findOne(fromOfficeId); + + final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element); + final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId); + + final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName); + final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId); + + final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element); + final Office toOffice = this.officeRepository.findOne(toOfficeId); + + final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element); + final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId); + + final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName); + final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId); + + final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName); + final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName); + final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), transactionAmount); + + final String description = command.stringValueOfParameterNamed(transferDescriptionParamName); + + return AccountTransfer.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toSavingsAccount, + withdrawal, deposit, transactionDate, transactionMonetaryAmount, description); + } + + public AccountTransfer assembleSavingsToLoanTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount, + final Loan toLoanAccount, final SavingsAccountTransaction withdrawal, final LoanTransaction loanRepaymentTransaction) { + + final JsonElement element = command.parsedJson(); + + final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element); + final Office fromOffice = this.officeRepository.findOne(fromOfficeId); + + final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element); + final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId); + + final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element); + final Office toOffice = this.officeRepository.findOne(toOfficeId); + + final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element); + final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId); + + final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName); + final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName); + final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), transactionAmount); + + final String description = command.stringValueOfParameterNamed(transferDescriptionParamName); + + return AccountTransfer.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount, + withdrawal, loanRepaymentTransaction, transactionDate, transactionMonetaryAmount, description); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferRepository.java new file mode 100644 index 000000000..352490aec --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferRepository.java @@ -0,0 +1,13 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.account.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface AccountTransferRepository extends JpaRepository, JpaSpecificationExecutor { + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/exception/AccountTransferNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/exception/AccountTransferNotFoundException.java new file mode 100644 index 000000000..5ac584ddd --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/exception/AccountTransferNotFoundException.java @@ -0,0 +1,15 @@ +/** + * 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.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class AccountTransferNotFoundException extends AbstractPlatformResourceNotFoundException { + + public AccountTransferNotFoundException(final Long id) { + super("error.msg.accounttransfer.id.invalid", "Account transfer with identifier " + id + " does not exist", id); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/handler/CreateAccountTransferCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/handler/CreateAccountTransferCommandHandler.java new file mode 100644 index 000000000..106de2e3e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/handler/CreateAccountTransferCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.account.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.account.service.AccountTransfersWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CreateAccountTransferCommandHandler implements NewCommandSourceHandler { + + private final AccountTransfersWritePlatformService writePlatformService; + + @Autowired + public CreateAccountTransferCommandHandler(final AccountTransfersWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return writePlatformService.create(command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransferEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransferEnumerations.java new file mode 100644 index 000000000..99fdf7335 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransferEnumerations.java @@ -0,0 +1,38 @@ +/** + * 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.service; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.account.PortfolioAccountType; + +public class AccountTransferEnumerations { + + public static EnumOptionData accountType(final Integer type) { + return accountType(PortfolioAccountType.fromInt(type)); + } + + public static EnumOptionData accountType(final PortfolioAccountType type) { + + EnumOptionData optionData = null; + + if (type != null) { + switch (type) { + case INVALID: + break; + case LOAN: + optionData = new EnumOptionData(PortfolioAccountType.LOAN.getValue().longValue(), PortfolioAccountType.LOAN.getCode(), + "Loan Account"); + break; + case SAVINGS: + optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(), + PortfolioAccountType.SAVINGS.getCode(), "Savings Account"); + break; + } + } + + return optionData; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformService.java new file mode 100644 index 000000000..a039841b1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformService.java @@ -0,0 +1,20 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.account.service; + +import org.mifosplatform.infrastructure.core.service.Page; +import org.mifosplatform.portfolio.account.data.AccountTransferData; +import org.mifosplatform.portfolio.group.service.SearchParameters; + +public interface AccountTransfersReadPlatformService { + + AccountTransferData retrieveTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType, + Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType); + + Page retrieveAll(SearchParameters searchParameters); + + AccountTransferData retrieveOne(Long transferId); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java new file mode 100644 index 000000000..166bb43df --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java @@ -0,0 +1,331 @@ +/** + * 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.service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.joda.time.LocalDate; +import org.mifosplatform.commands.exception.UnsupportedCommandException; +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.infrastructure.core.domain.JdbcSupport; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.infrastructure.core.service.Page; +import org.mifosplatform.infrastructure.core.service.PaginationHelper; +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.organisation.office.data.OfficeData; +import org.mifosplatform.organisation.office.service.OfficeReadPlatformService; +import org.mifosplatform.portfolio.account.PortfolioAccountType; +import org.mifosplatform.portfolio.account.data.AccountTransferData; +import org.mifosplatform.portfolio.account.data.PortfolioAccountData; +import org.mifosplatform.portfolio.account.exception.AccountTransferNotFoundException; +import org.mifosplatform.portfolio.client.data.ClientData; +import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; +import org.mifosplatform.portfolio.group.service.SearchParameters; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +@Service +public class AccountTransfersReadPlatformServiceImpl implements AccountTransfersReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + private final ClientReadPlatformService clientReadPlatformService; + private final OfficeReadPlatformService officeReadPlatformService; + private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService; + + // mapper + private final AccountTransfersMapper accountTransfersMapper; + + // pagination + private final PaginationHelper paginationHelper = new PaginationHelper(); + + @Autowired + public AccountTransfersReadPlatformServiceImpl(final RoutingDataSource dataSource, + final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService, + final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.clientReadPlatformService = clientReadPlatformService; + this.officeReadPlatformService = officeReadPlatformService; + this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService; + + this.accountTransfersMapper = new AccountTransfersMapper(); + } + + @Override + public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId, + final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) { + + final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); + final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); + + final Collection fromAccountTypeOptions = Arrays.asList(savingsAccountType); + final Collection toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType); + + // TODO - remove this after doing overpaid loan to savings transfers + Integer mostRelevantFromAccountType = fromAccountType; + if (mostRelevantFromAccountType == null) { + mostRelevantFromAccountType = 2; // savings + } + + final Integer mostRelevantToAccountType = toAccountType; + + final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType); + final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType); + + // check for now that only savings account type is passed and used. + if (mostRelevantFromAccountType != 2) { throw new UnsupportedCommandException("transferFromLoanAccountNotSupported"); } + + // from settings + OfficeData fromOffice = null; + ClientData fromClient = null; + PortfolioAccountData fromAccount = null; + + OfficeData toOffice = null; + ClientData toClient = null; + PortfolioAccountData toAccount = null; + + // template + Collection fromAccountOptions = null; + Collection toAccountOptions = null; + + Long mostRelevantFromOfficeId = fromOfficeId; + Long mostRelevantFromClientId = fromClientId; + + Long mostRelevantToOfficeId = toOfficeId; + Long mostRelevantToClientId = toClientId; + + if (fromAccountId != null && mostRelevantFromAccountType == 2) { + fromAccount = this.portfolioAccountReadPlatformService.retrieveOne(fromAccountId, PortfolioAccountType.SAVINGS.getValue()); + + // override provided fromClient with client of account + mostRelevantFromClientId = fromAccount.clientId(); + } else if (fromAccountId != null && mostRelevantFromAccountType == 1) { // loans + // fetch loan account + } + + if (mostRelevantFromClientId != null) { + fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId); + mostRelevantFromOfficeId = fromClient.officeId(); + + fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(mostRelevantFromAccountType, + mostRelevantFromClientId); + } + + Collection fromOfficeOptions = null; + Collection fromClientOptions = null; + if (mostRelevantFromOfficeId != null) { + fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId); + fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown(); + fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId); + } + + // defaults + final LocalDate transferDate = DateUtils.getLocalDateOfTenant(); + Collection toOfficeOptions = fromOfficeOptions; + Collection toClientOptions = null; + + if (toAccountId != null && fromAccount != null) { + toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType, fromAccount.currencyCode()); + mostRelevantToClientId = toAccount.clientId(); + } + + if (mostRelevantToClientId != null) { + toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId); + mostRelevantToOfficeId = toClient.officeId(); + + toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); + + toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); + } + + if (mostRelevantToOfficeId != null) { + toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId); + toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown(); + + toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); + if (toClientOptions != null && toClientOptions.size() == 1) { + toClient = new ArrayList(toClientOptions).get(0); + + toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); + } + } + + return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient, + toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions, + toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions); + } + + private Collection retrieveToAccounts(final PortfolioAccountData excludeThisAccountFromOptions, + final Integer toAccountType, final Long toClientId) { + + final String currencyCode = excludeThisAccountFromOptions != null ? excludeThisAccountFromOptions.currencyCode() : null; + + Collection accountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(toAccountType, + toClientId, currencyCode); + if (!CollectionUtils.isEmpty(accountOptions)) { + accountOptions.remove(excludeThisAccountFromOptions); + } else { + accountOptions = null; + } + + return accountOptions; + } + + @Override + public Page retrieveAll(final SearchParameters searchParameters) { + + final StringBuilder sqlBuilder = new StringBuilder(200); + sqlBuilder.append("select SQL_CALC_FOUND_ROWS "); + sqlBuilder.append(this.accountTransfersMapper.schema()); + + if (searchParameters.isOrderByRequested()) { + sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); + + if (searchParameters.isSortOrderProvided()) { + sqlBuilder.append(' ').append(searchParameters.getSortOrder()); + } + } + + if (searchParameters.isLimited()) { + sqlBuilder.append(" limit ").append(searchParameters.getLimit()); + if (searchParameters.isOffset()) { + sqlBuilder.append(" offset ").append(searchParameters.getOffset()); + } + } + + final Object[] objectArray = new Object[2]; + final int arrayPos = 0; + final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos); + final String sqlCountRows = "SELECT FOUND_ROWS()"; + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray, + this.accountTransfersMapper); + } + + @Override + public AccountTransferData retrieveOne(final Long transferId) { + + try { + final String sql = "select " + this.accountTransfersMapper.schema() + " where sat.id = ?"; + + return this.jdbcTemplate.queryForObject(sql, this.accountTransfersMapper, new Object[] { transferId }); + } catch (final EmptyResultDataAccessException e) { + throw new AccountTransferNotFoundException(transferId); + } + } + + private static final class AccountTransfersMapper implements RowMapper { + + private final String schemaSql; + + public AccountTransfersMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("sat.id as id, sat.is_reversed as isReversed,"); + sqlBuilder.append("sat.transaction_date as transferDate, sat.amount as transferAmount,"); + sqlBuilder.append("sat.description as transferDescription,"); + sqlBuilder.append("sat.currency_code as currencyCode, sat.currency_digits as currencyDigits,"); + sqlBuilder.append("sat.currency_multiplesof as inMultiplesOf, "); + sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); + sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,"); + sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,"); + sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,"); + sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,"); + sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,"); + sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,"); + sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,"); + sqlBuilder.append("fromsavtran.id as fromSavingsAccountTransactionId,"); + sqlBuilder.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,"); + sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,"); + sqlBuilder.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType"); + sqlBuilder.append(" FROM m_savings_account_transfer sat "); + sqlBuilder.append("join m_currency curr on curr.code = sat.currency_code "); + sqlBuilder.append("join m_office fromoff on fromoff.id = sat.from_office_id "); + sqlBuilder.append("join m_office tooff on tooff.id = sat.to_office_id "); + sqlBuilder.append("join m_client fromclient on fromclient.id = sat.from_client_id "); + sqlBuilder.append("join m_client toclient on toclient.id = sat.to_client_id "); + sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = sat.from_savings_account_id "); + sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = sat.to_savings_account_id "); + sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = sat.to_loan_account_id "); + sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = sat.from_savings_transaction_id "); + sqlBuilder.append("left join m_savings_account_transaction tosavtran on tosavtran.id = sat.to_savings_transaction_id "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public AccountTransferData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong("id"); + final boolean reversed = rs.getBoolean("isReversed"); + + final LocalDate transferDate = JdbcSupport.getLocalDate(rs, "transferDate"); + final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transferAmount"); + final String transferDescription = rs.getString("transferDescription"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); + + final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId"); + final String fromOfficeName = rs.getString("fromOfficeName"); + final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null); + + final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId"); + final String toOfficeName = rs.getString("toOfficeName"); + final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null); + + final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId"); + final String fromClientName = rs.getString("fromClientName"); + final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName); + + final Long toClientId = JdbcSupport.getLong(rs, "toClientId"); + final String toClientName = rs.getString("toClientName"); + final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName); + + final EnumOptionData fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); + + final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId"); + final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo"); + final PortfolioAccountData fromSavingsAccount = PortfolioAccountData.lookup(fromSavingsAccountId, fromSavingsAccountNo); + + PortfolioAccountData toAccount = null; + EnumOptionData toAccountType = null; + final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId"); + final String toSavingsAccountNo = rs.getString("toSavingsAccountNo"); + final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId"); + final String toLoanAccountNo = rs.getString("toLoanAccountNo"); + + if (toSavingsAccountId != null) { + toAccount = PortfolioAccountData.lookup(toSavingsAccountId, toSavingsAccountNo); + toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); + } else if (toLoanAccountId != null) { + toAccount = PortfolioAccountData.lookup(toLoanAccountId, toLoanAccountNo); + toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); + } + + return AccountTransferData.instance(id, reversed, transferDate, currency, transferAmount, transferDescription, fromOffice, + toOffice, fromClient, toClient, fromAccountType, fromSavingsAccount, toAccountType, toAccount); + } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java new file mode 100644 index 000000000..821478df9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java @@ -0,0 +1,14 @@ +/** + * 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.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public interface AccountTransfersWritePlatformService { + + CommandProcessingResult create(JsonCommand command); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java new file mode 100644 index 000000000..fed01d77b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -0,0 +1,153 @@ +/** + * 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.service; + +import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountIdParamName; +import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountTypeParamName; +import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountIdParamName; +import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountTypeParamName; +import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferAmountParamName; +import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferDateParamName; + +import java.math.BigDecimal; +import java.util.Locale; + +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.portfolio.account.PortfolioAccountType; +import org.mifosplatform.portfolio.account.data.AccountTransfersDataValidator; +import org.mifosplatform.portfolio.account.domain.AccountTransfer; +import org.mifosplatform.portfolio.account.domain.AccountTransferAssembler; +import org.mifosplatform.portfolio.account.domain.AccountTransferRepository; +import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; +import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.mifosplatform.portfolio.savings.domain.SavingsAccount; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountDomainService; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class AccountTransfersWritePlatformServiceImpl implements AccountTransfersWritePlatformService { + + private final AccountTransfersDataValidator accountTransfersDataValidator; + private final AccountTransferAssembler accountTransferAssembler; + private final AccountTransferRepository accountTransferRepository; + private final SavingsAccountAssembler savingsAccountAssembler; + private final SavingsAccountDomainService savingsAccountDomainService; + private final LoanAssembler loanAccountAssembler; + private final LoanAccountDomainService loanAccountDomainService; + + @Autowired + public AccountTransfersWritePlatformServiceImpl(final AccountTransfersDataValidator accountTransfersDataValidator, + final AccountTransferAssembler accountTransferAssembler, final AccountTransferRepository accountTransferRepository, + final SavingsAccountAssembler savingsAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService, + final LoanAssembler loanAssembler, final LoanAccountDomainService loanAccountDomainService) { + this.accountTransfersDataValidator = accountTransfersDataValidator; + this.accountTransferAssembler = accountTransferAssembler; + this.accountTransferRepository = accountTransferRepository; + this.savingsAccountAssembler = savingsAccountAssembler; + this.savingsAccountDomainService = savingsAccountDomainService; + this.loanAccountAssembler = loanAssembler; + this.loanAccountDomainService = loanAccountDomainService; + } + + @Transactional + @Override + public CommandProcessingResult create(final JsonCommand command) { + + this.accountTransfersDataValidator.validate(command); + + final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName); + final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName); + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + final Integer fromAccountTypeId = command.integerValueSansLocaleOfParameterNamed(fromAccountTypeParamName); + final PortfolioAccountType fromAccountType = PortfolioAccountType.fromInt(fromAccountTypeId); + + final Integer toAccountTypeId = command.integerValueSansLocaleOfParameterNamed(toAccountTypeParamName); + final PortfolioAccountType toAccountType = PortfolioAccountType.fromInt(toAccountTypeId); + + final PaymentDetail paymentDetail = null; + Long fromSavingsAccountId = null; + Long transferTransactionId = null; + if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) { + + fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); + final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId); + + final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt, + transactionDate, transactionAmount, paymentDetail); + + final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName); + final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId); + + final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, + transactionDate, transactionAmount, paymentDetail); + + final AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToSavingsTransfer(command, withdrawal, + deposit); + this.accountTransferRepository.saveAndFlush(transferTransaction); + transferTransactionId = transferTransaction.getId(); + + } else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) { + // + fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); + final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId); + + final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt, + transactionDate, transactionAmount, paymentDetail); + + final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName); + final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId); + + final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccountId, + new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null); + + final AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command, + fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction); + this.accountTransferRepository.saveAndFlush(transferTransaction); + transferTransactionId = transferTransaction.getId(); + + } else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) { + // FIXME - kw - ADD overpaid loan to savings account transfer support. + } else { + + } + + final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(transferTransactionId); + + if (fromAccountType.isSavingsAccount()) { + + builder.withSavingsId(fromSavingsAccountId); + } + + return builder.build(); + } + + private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { + return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount(); + } + + private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { + return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount(); + } + + private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { + return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformService.java new file mode 100644 index 000000000..7fc417b2e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformService.java @@ -0,0 +1,21 @@ +/** + * 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.service; + +import java.util.Collection; + +import org.mifosplatform.portfolio.account.data.PortfolioAccountData; + +public interface PortfolioAccountReadPlatformService { + + PortfolioAccountData retrieveOne(Long accountId, Integer accountTypeId); + + PortfolioAccountData retrieveOne(Long accountId, Integer accountTypeId, String currencyCode); + + Collection retrieveAllForLookup(Integer toAccountType, Long toClientId); + + Collection retrieveAllForLookup(Integer toAccountType, Long toClientId, String currencyCode); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java new file mode 100644 index 000000000..5eb5906bc --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java @@ -0,0 +1,248 @@ +/** + * 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.service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; + +import org.mifosplatform.infrastructure.core.domain.JdbcSupport; +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.account.PortfolioAccountType; +import org.mifosplatform.portfolio.account.data.PortfolioAccountData; +import org.mifosplatform.portfolio.account.exception.AccountTransferNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccountReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + + // mapper + private final PortfolioSavingsAccountMapper savingsAccountMapper; + private final PortfolioLoanAccountMapper loanAccountMapper; + + @Autowired + public PortfolioAccountReadPlatformServiceImpl(final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.savingsAccountMapper = new PortfolioSavingsAccountMapper(); + this.loanAccountMapper = new PortfolioLoanAccountMapper(); + } + + @Override + public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId) { + return retrieveOne(accountId, accountTypeId, null); + } + + @Override + public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId, final String currencyCode) { + + Object[] sqlParams = new Object[] {accountId}; + PortfolioAccountData accountData = null; + try { + String sql = null; + final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId); + switch (accountType) { + case INVALID: + break; + case LOAN: + + sql = "select " + this.loanAccountMapper.schema() + " where la.id = ?"; + if (currencyCode != null) { + sql += " and la.currency_code = ?"; + sqlParams = new Object[] {accountId, currencyCode}; + } + + accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams); + break; + case SAVINGS: + sql = "select " + this.savingsAccountMapper.schema() + " where sa.id = ?"; + if (currencyCode != null) { + sql += " and la.currency_code = ?"; + sqlParams = new Object[] {accountId, currencyCode}; + } + + accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams); + break; + default: + break; + } + } catch (final EmptyResultDataAccessException e) { + throw new AccountTransferNotFoundException(accountId); + } + + return accountData; + } + + @Override + public Collection retrieveAllForLookup(final Integer accountTypeId, final Long clientId) { + return retrieveAllForLookup(accountTypeId, clientId, null); + } + + @Override + public Collection retrieveAllForLookup(final Integer accountTypeId, final Long clientId, final String currencyCode) { + + Object[] sqlParams = new Object[] {clientId}; + Collection accounts = null; + String sql = null; + final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId); + switch (accountType) { + case INVALID: + break; + case LOAN: + sql = "select " + this.loanAccountMapper.schema() + " where la.client_id = ? and la.loan_status_id = 300"; + if (currencyCode != null) { + sql += " and la.currency_code = ?"; + sqlParams = new Object[] {clientId, currencyCode}; + } + + accounts = this.jdbcTemplate.query(sql, this.loanAccountMapper, sqlParams); + break; + case SAVINGS: + sql = "select " + this.savingsAccountMapper.schema() + " where sa.client_id = ? and sa.status_enum = 300"; + if (currencyCode != null) { + sql += " and sa.currency_code = ?"; + sqlParams = new Object[] {clientId, currencyCode}; + } + + accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams); + break; + default: + break; + } + + return accounts; + } + + private static final class PortfolioSavingsAccountMapper implements RowMapper { + + private final String schemaSql; + + public PortfolioSavingsAccountMapper() { + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); + sqlBuilder.append("c.id as clientId, c.display_name as clientName, "); + sqlBuilder.append("g.id as groupId, g.display_name as groupName, "); + sqlBuilder.append("sp.id as productId, sp.name as productName, "); + sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, "); + sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits,"); + sqlBuilder.append("sa.currency_multiplesof as inMultiplesOf, "); + sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol "); + sqlBuilder.append("from m_savings_account sa "); + sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); + sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + sqlBuilder.append("left join m_client c ON c.id = sa.client_id "); + sqlBuilder.append("left join m_group g ON g.id = sa.group_id "); + sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong("id"); + final String accountNo = rs.getString("accountNo"); + final String externalId = rs.getString("externalId"); + + final Long groupId = JdbcSupport.getLong(rs, "groupId"); + final String groupName = rs.getString("groupName"); + final Long clientId = JdbcSupport.getLong(rs, "clientId"); + final String clientName = rs.getString("clientName"); + + final Long productId = rs.getLong("productId"); + final String productName = rs.getString("productName"); + + final Long fieldOfficerId = rs.getLong("fieldOfficerId"); + final String fieldOfficerName = rs.getString("fieldOfficerName"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, + currencyDisplaySymbol, currencyNameCode); + + return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId, fieldOfficerName, currency); + } + } + + private static final class PortfolioLoanAccountMapper implements RowMapper { + + private final String schemaSql; + + public PortfolioLoanAccountMapper() { + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, "); + sqlBuilder.append("c.id as clientId, c.display_name as clientName, "); + sqlBuilder.append("g.id as groupId, g.display_name as groupName, "); + sqlBuilder.append("lp.id as productId, lp.name as productName, "); + sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, "); + sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,"); + sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, "); + sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol "); + sqlBuilder.append("from m_loan la "); + sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id "); + sqlBuilder.append("join m_currency curr on curr.code = la.currency_code "); + sqlBuilder.append("left join m_client c ON c.id = la.client_id "); + sqlBuilder.append("left join m_group g ON g.id = la.group_id "); + sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong("id"); + final String accountNo = rs.getString("accountNo"); + final String externalId = rs.getString("externalId"); + + final Long groupId = JdbcSupport.getLong(rs, "groupId"); + final String groupName = rs.getString("groupName"); + final Long clientId = JdbcSupport.getLong(rs, "clientId"); + final String clientName = rs.getString("clientName"); + + final Long productId = rs.getLong("productId"); + final String productName = rs.getString("productName"); + + final Long fieldOfficerId = rs.getLong("fieldOfficerId"); + final String fieldOfficerName = rs.getString("fieldOfficerName"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, + currencyDisplaySymbol, currencyNameCode); + + return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId, fieldOfficerName, currency); + } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/AccountSummaryCollectionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/AccountSummaryCollectionData.java new file mode 100644 index 000000000..dd4b59650 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/AccountSummaryCollectionData.java @@ -0,0 +1,57 @@ +/** + * 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.accountdetails.data; + +import java.util.Collection; + +/** + * Immutable data object representing a summary of various accounts. + */ +public class AccountSummaryCollectionData { + + @SuppressWarnings("unused") + private final Collection loanAccounts; + @SuppressWarnings("unused") + private final Collection savingsAccounts; + @SuppressWarnings("unused") + private final Collection memberLoanAccounts; + @SuppressWarnings("unused") + private final Collection memberSavingsAccounts; + + public AccountSummaryCollectionData(final Collection loanAccounts, + final Collection savingsAccounts) { + this.loanAccounts = defaultLoanAccountsIfEmpty(loanAccounts); + this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts); + this.memberLoanAccounts = null; + this.memberSavingsAccounts = null; + } + + public AccountSummaryCollectionData(final Collection loanAccounts, + final Collection savingsAccounts, final Collection memberLoanAccounts, + Collection memberSavingsAccounts) { + this.loanAccounts = defaultLoanAccountsIfEmpty(loanAccounts); + this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts); + this.memberLoanAccounts = defaultLoanAccountsIfEmpty(memberLoanAccounts); + this.memberSavingsAccounts = defaultSavingsAccountsIfEmpty(memberSavingsAccounts); + } + + private Collection defaultLoanAccountsIfEmpty(final Collection collection) { + Collection returnCollection = null; + if (collection != null && !collection.isEmpty()) { + returnCollection = collection; + } + return returnCollection; + } + + private Collection defaultSavingsAccountsIfEmpty(final Collection collection) { + Collection returnCollection = null; + if (collection != null && !collection.isEmpty()) { + returnCollection = collection; + } + return returnCollection; + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientLoanAccountSummaryData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/LoanAccountSummaryData.java similarity index 80% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientLoanAccountSummaryData.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/LoanAccountSummaryData.java index 990de0c62..15a64d472 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientLoanAccountSummaryData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/LoanAccountSummaryData.java @@ -3,16 +3,16 @@ * 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.client.data; +package org.mifosplatform.portfolio.accountdetails.data; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData; /** - * Immutable data object for client loan accounts. + * Immutable data object for loan accounts. */ @SuppressWarnings("unused") -public class ClientLoanAccountSummaryData { +public class LoanAccountSummaryData { private final Long id; private final String accountNo; @@ -23,7 +23,7 @@ public class ClientLoanAccountSummaryData { private final EnumOptionData loanType; private final Integer loanCycle; - public ClientLoanAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId, + public LoanAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId, final String loanProductName, final LoanStatusEnumData loanStatus, final EnumOptionData loanType, final Integer loanCycle) { this.id = id; this.accountNo = accountNo; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientSavingsAccountSummaryData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/SavingsAccountSummaryData.java similarity index 69% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientSavingsAccountSummaryData.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/SavingsAccountSummaryData.java index 711969e04..ad8fee7fc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientSavingsAccountSummaryData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/data/SavingsAccountSummaryData.java @@ -3,18 +3,19 @@ * 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.client.data; +package org.mifosplatform.portfolio.accountdetails.data; import java.math.BigDecimal; +import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; /** - * Immutable data object for client savings accounts. + * Immutable data object for savings accounts. */ @SuppressWarnings("unused") -public class ClientSavingsAccountSummaryData { +public class SavingsAccountSummaryData { private final Long id; private final String accountNo; @@ -24,10 +25,11 @@ public class ClientSavingsAccountSummaryData { private final SavingsAccountStatusEnumData status; private final CurrencyData currency; private final BigDecimal accountBalance; + private final EnumOptionData accountType; - public ClientSavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId, + public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId, final String productName, final SavingsAccountStatusEnumData status, final CurrencyData currency, - final BigDecimal accountBalance) { + final BigDecimal accountBalance, final EnumOptionData accountType) { this.id = id; this.accountNo = accountNo; this.externalId = externalId; @@ -36,5 +38,6 @@ public class ClientSavingsAccountSummaryData { this.status = status; this.currency = currency; this.accountBalance = accountBalance; + this.accountType = accountType; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/domain/AccountType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/domain/AccountType.java new file mode 100644 index 000000000..02ba6955e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/domain/AccountType.java @@ -0,0 +1,81 @@ +/** + * 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.accountdetails.domain; + +/** + * Enum representation of account types . + */ +public enum AccountType { + + INVALID(0, "accountType.invalid"), // + INDIVIDUAL(1, "accountType.individual"), // + GROUP(2, "accountType.group"), // + JLG(3, "accountType.jlg");//JLG account given in group context + + private final Integer value; + private final String code; + + private AccountType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public static AccountType fromInt(final Integer accountTypeValue) { + + AccountType enumeration = AccountType.INVALID; + switch (accountTypeValue) { + case 1: + enumeration = AccountType.INDIVIDUAL; + break; + case 2: + enumeration = AccountType.GROUP; + break; + case 3: + enumeration = AccountType.JLG; + break; + } + return enumeration; + } + + public static AccountType fromName(final String name){ + AccountType accountType = AccountType.INVALID; + for (AccountType type : AccountType.values()) { + if(type.getName().equals(name)) { + accountType = type; + break; + } + } + return accountType; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return code; + } + + public String getName(){ + return name().toLowerCase(); + } + + public boolean isInvalid(){ + return this.value.equals(AccountType.INVALID.getValue()); + } + + public boolean isIndividualAccount(){ + return this.value.equals(AccountType.INDIVIDUAL.getValue()); + } + + public boolean isGroupAccount(){ + return this.value.equals(AccountType.GROUP.getValue()); + } + + public boolean isJLGAccount(){ + return this.value.equals(AccountType.JLG.getValue()); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountDetailsReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountDetailsReadPlatformService.java new file mode 100755 index 000000000..ad918f046 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountDetailsReadPlatformService.java @@ -0,0 +1,18 @@ +package org.mifosplatform.portfolio.accountdetails.service; + +import java.util.Collection; + +import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData; +import org.mifosplatform.portfolio.accountdetails.data.LoanAccountSummaryData; + + +public interface AccountDetailsReadPlatformService { + + public AccountSummaryCollectionData retrieveClientAccountDetails(final Long clientId); + + public AccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId); + + public Collection retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId); + + public Collection retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId,final Long loanOfficerId); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java new file mode 100755 index 000000000..4952e81d8 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,184 @@ +package org.mifosplatform.portfolio.accountdetails.service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.infrastructure.core.domain.JdbcSupport; +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData; +import org.mifosplatform.portfolio.accountdetails.data.LoanAccountSummaryData; +import org.mifosplatform.portfolio.accountdetails.data.SavingsAccountSummaryData; +import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; +import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; +import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData; +import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; +import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; +import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements AccountDetailsReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + private final ClientReadPlatformService clientReadPlatformService; + private final GroupReadPlatformService groupReadPlatformService; + + @Autowired + public AccountDetailsReadPlatformServiceJpaRepositoryImpl(final ClientReadPlatformService clientReadPlatformService, + final RoutingDataSource dataSource, final GroupReadPlatformService groupReadPlatformService) { + this.clientReadPlatformService = clientReadPlatformService; + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.groupReadPlatformService = groupReadPlatformService; + } + + @Override + public AccountSummaryCollectionData retrieveClientAccountDetails(Long clientId) { + // Check if client exists + clientReadPlatformService.retrieveOne(clientId); + String loanwhereClause = " where l.client_id = ?"; + String savingswhereClause = " where sa.client_id = ? order by sa.status_enum ASC, sa.account_no ASC"; + List loanAccounts = retrieveLoanAccountDetails(loanwhereClause, new Object[] { clientId }); + List savingsAccounts = retrieveAccountDetails(savingswhereClause, new Object[] { clientId }); + return new AccountSummaryCollectionData(loanAccounts, savingsAccounts); + } + + @Override + public AccountSummaryCollectionData retrieveGroupAccountDetails(Long groupId) { + // Check if group exists + groupReadPlatformService.retrieveOne(groupId); + String loanWhereClauseForGroup = " where l.group_id = ? and l.client_id is null"; + String loanWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null"; + String savingswhereClauseForGroup = " where sa.group_id = ? and sa.client_id is null order by sa.status_enum ASC, sa.account_no ASC"; + String savingswhereClauseForMembers = " where sa.group_id = ? and sa.client_id is not null order by sa.status_enum ASC, sa.account_no ASC"; + List groupLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroup, new Object[] { groupId }); + List groupSavingsAccounts = retrieveAccountDetails(savingswhereClauseForGroup, new Object[] { groupId }); + List memberLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForMembers, new Object[] { groupId }); + List memberSavingsAccounts = retrieveAccountDetails(savingswhereClauseForMembers, + new Object[] { groupId }); + return new AccountSummaryCollectionData(groupLoanAccounts, groupSavingsAccounts, memberLoanAccounts, memberSavingsAccounts); + } + + @Override + public Collection retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId) { + // Check if client exists + clientReadPlatformService.retrieveOne(clientId); + String loanWhereClause = " where l.client_id = ? and l.loan_officer_id = ?"; + return retrieveLoanAccountDetails(loanWhereClause, new Object[] { clientId, loanOfficerId }); + } + + @Override + public Collection retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId, final Long loanOfficerId) { + // Check if group exists + groupReadPlatformService.retrieveOne(groupId); + String loanWhereClause = " where l.group_id = ? and l.client_id is null and l.loan_officer_id = ?"; + return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId, loanOfficerId }); + } + + private List retrieveLoanAccountDetails(String loanwhereClause, Object[] inputs) { + LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper(); + String sql = "select " + rm.loanAccountSummarySchema() + loanwhereClause; + return this.jdbcTemplate.query(sql, rm, inputs); + } + + /** + * @param entityId + * @return + */ + private List retrieveAccountDetails(String savingswhereClause, Object[] inputs) { + SavingsAccountSummaryDataMapper savingsAccountSummaryDataMapper = new SavingsAccountSummaryDataMapper(); + final String savingsSql = "select " + savingsAccountSummaryDataMapper.schema() + savingswhereClause; + return this.jdbcTemplate.query(savingsSql, savingsAccountSummaryDataMapper, inputs); + } + + private static final class SavingsAccountSummaryDataMapper implements RowMapper { + + final String schemaSql; + + public SavingsAccountSummaryDataMapper() { + StringBuilder accountsSummary = new StringBuilder(); + accountsSummary.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, sa.status_enum as statusEnum, "); + accountsSummary.append("sa.account_type_enum as accountType, "); + accountsSummary.append("sa.account_balance_derived as accountBalance, "); + accountsSummary + .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); + accountsSummary.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + accountsSummary.append("curr.display_symbol as currencyDisplaySymbol, "); + accountsSummary.append("sa.product_id as productId, p.name as productName "); + accountsSummary.append("from m_savings_account sa "); + accountsSummary.append("join m_savings_product as p on p.id = sa.product_id "); + accountsSummary.append("join m_currency curr on curr.code = sa.currency_code "); + + this.schemaSql = accountsSummary.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = JdbcSupport.getLong(rs, "id"); + final String accountNo = rs.getString("accountNo"); + final String externalId = rs.getString("externalId"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final String productName = rs.getString("productName"); + final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); + final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance"); + final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId); + final Integer accountType = JdbcSupport.getInteger(rs, "accountType"); + final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); + + return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, status, currency, accountBalance, + accountTypeData); + } + } + + private static final class LoanAccountSummaryDataMapper implements RowMapper { + + public String loanAccountSummarySchema() { + + StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,"); + accountsSummary.append("l.product_id as productId, lp.name as productName,") + .append("l.loan_status_id as statusId, l.loan_type_enum as loanType, ").append("l.loan_product_counter as loanCycle ") + .append(" from m_loan l ").append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id"); + + return accountsSummary.toString(); + } + + @Override + public LoanAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = JdbcSupport.getLong(rs, "id"); + final String accountNo = rs.getString("accountNo"); + final String externalId = rs.getString("externalId"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final String loanProductName = rs.getString("productName"); + final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId"); + final LoanStatusEnumData loanStatus = LoanEnumerations.status(loanStatusId); + final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType"); + final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId); + final Integer loanCycle = JdbcSupport.getInteger(rs, "loanCycle"); + + return new LoanAccountSummaryData(id, accountNo, externalId, productId, loanProductName, loanStatus, loanType, loanCycle); + } + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountEnumerations.java new file mode 100755 index 000000000..119d3c8a4 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/accountdetails/service/AccountEnumerations.java @@ -0,0 +1,38 @@ +package org.mifosplatform.portfolio.accountdetails.service; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.accountdetails.domain.AccountType; + + +public class AccountEnumerations { + + public static EnumOptionData loanType(final Integer loanTypeId) { + return loanType(AccountType.fromInt(loanTypeId)); + } + + public static EnumOptionData loanType(final String name) { + return loanType(AccountType.fromName(name)); + } + + public static EnumOptionData loanType(final AccountType type) { + EnumOptionData optionData = new EnumOptionData(AccountType.INVALID.getValue().longValue(), AccountType.INVALID.getCode(), "Invalid"); + switch (type) { + case INVALID: + optionData = new EnumOptionData(AccountType.INVALID.getValue().longValue(), AccountType.INVALID.getCode(), "Invalid"); + break; + case INDIVIDUAL: + optionData = new EnumOptionData(AccountType.INDIVIDUAL.getValue().longValue(), AccountType.INDIVIDUAL.getCode(), "Individual"); + break; + case GROUP: + optionData = new EnumOptionData(AccountType.GROUP.getValue().longValue(), AccountType.GROUP.getCode(), "Group"); + break; + case JLG: + optionData = new EnumOptionData(AccountType.JLG.getValue().longValue(), AccountType.JLG.getCode(), "JLG"); + break; + } + + return optionData; + } + + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java index d7a80ef7e..5afcb3311 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java @@ -20,6 +20,7 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.service.CalendarDropdownReadPlatformService; import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -105,7 +106,7 @@ public class CalendarsApiResource { Collection calendarsData = new ArrayList(); - List calendarTypeOptions = createIntegerListFromQueryParameter(calendarType); + List calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter(calendarType); if (!associationParameters.isEmpty()) { @@ -191,37 +192,4 @@ public class CalendarsApiResource { return new CalendarData(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions); } - - public List createIntegerListFromQueryParameter(String calendarTypeQuery) { - List calendarTypeOptions = new ArrayList(); - // adding all calendar Types if query parameter is "all" - if(calendarTypeQuery.equalsIgnoreCase("all")){ - calendarTypeOptions.add(1); - calendarTypeOptions.add(2); - calendarTypeOptions.add(3); - calendarTypeOptions.add(4); - return calendarTypeOptions; - } - // creating a list of calendar type options from the comma separated query parameter. - List calendarTypeOptionsInQuery = new ArrayList(); - StringTokenizer st = new StringTokenizer(calendarTypeQuery, ","); - while (st.hasMoreElements()) { - calendarTypeOptionsInQuery.add(st.nextElement().toString()); - } - - for(String calType : calendarTypeOptionsInQuery){ - if(calType.equalsIgnoreCase("collection")) { - calendarTypeOptions.add(1); - } else if(calType.equalsIgnoreCase("training")) { - calendarTypeOptions.add(2); - } else if(calType.equalsIgnoreCase("audit")) { - calendarTypeOptions.add(3); - } else if(calType.equalsIgnoreCase("general")) { - calendarTypeOptions.add(4); - } - } - - return calendarTypeOptions; - } - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java index 49395caf0..eb25c0a79 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java @@ -23,7 +23,7 @@ import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom; import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS; import org.mifosplatform.portfolio.calendar.exception.CalendarDateException; import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException; -import org.mifosplatform.portfolio.calendar.service.CalendarHelper; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.useradministration.domain.AppUser; @Entity @@ -213,13 +213,13 @@ public class Calendar extends AbstractAuditableCustom { //FIXME: AA - Is this restriction required only for collection type meetings or for all?. //Do not allow to change meeting frequency - if(!CalendarHelper.isFrequencySame(this.recurrence, newValue)){ + if(!CalendarUtils.isFrequencySame(this.recurrence, newValue)){ final String defaultUserMessage = "Update of meeting frequency is not supported"; throw new CalendarParameterUpdateNotSupportedException("meeting.frequency", defaultUserMessage); } //Do not allow to change meeting interval - if(!CalendarHelper.isIntervalSame(this.recurrence, newValue)){ + if(!CalendarUtils.isIntervalSame(this.recurrence, newValue)){ final String defaultUserMessage = "Update of meeting interval is not supported"; throw new CalendarParameterUpdateNotSupportedException("meeting.interval", defaultUserMessage); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java index b893a1b93..95cf5329d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java @@ -66,4 +66,20 @@ public enum CalendarEntityType { public String toString() { return name().toString(); } + + public static boolean isGroup(final Integer value) { + return CalendarEntityType.GROUPS.value.equals(value) ? true : false; + } + + public static boolean isGroup(final String name) { + return CalendarEntityType.GROUPS.name().equalsIgnoreCase(name) ? true : false; + } + + public static boolean isCenter(final Integer value) { + return CalendarEntityType.CENTERS.value.equals(value) ? true : false; + } + + public static boolean isCenter(final String name) { + return CalendarEntityType.CENTERS.name().equalsIgnoreCase(name) ? true : false; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java index f11547652..1a3a3b4c9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java @@ -6,6 +6,7 @@ package org.mifosplatform.portfolio.calendar.domain; import java.util.Collection; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; @@ -14,12 +15,19 @@ import org.springframework.data.repository.query.Param; public interface CalendarInstanceRepository extends JpaRepository, JpaSpecificationExecutor { - CalendarInstance findByCalendarAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId); + CalendarInstance findByCalendarIdAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId); Collection findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId); + CalendarInstance findByEntityIdAndEntityTypeIdAndCalendarTypeId(Long entityId, Integer entityTypeId, Integer calendarTypeId); + @Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId") CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId); - + Collection findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId); + + /**Should use in clause, can I do it without creating a new class?**/ + @Query("from CalendarInstance ci where ci.entityId in (select id from Loan loan where loan.client.id = :clientId and loan.group.id=:groupId and (loan.loanStatus = 100 or loan.loanStatus = 200 or loan.loanStatus = 300)) and ci.entityTypeId = 3") + List findCalendarInstancesForActiveLoansByGroupIdAndClientId(@Param("clientId") Long clientId, + @Param("groupId") Long groupId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java new file mode 100644 index 000000000..ce94b7088 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java @@ -0,0 +1,51 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.calendar.domain; + +import org.mifosplatform.portfolio.calendar.exception.CalendarInstanceNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * Wrapper for {@link CalendarInstanceRepository} that is responsible for checking + * if {@link CalendarInstance} is returned when using findOne + * repository method and throwing an appropriate not found exception. + *

+ * + *

+ * This is to avoid need for checking and throwing in multiple areas of code + * base where {@link CalendarInstanceRepository} is required. + *

+ */ +@Service +public class CalendarInstanceRepositoryWrapper { + + private final CalendarInstanceRepository repository; + + @Autowired + public CalendarInstanceRepositoryWrapper(final CalendarInstanceRepository repository) { + this.repository = repository; + } + + public CalendarInstance findOneWithNotFoundDetection(final Long CalendarInstanceId) { + final CalendarInstance calendatInstance = this.repository.findOne(CalendarInstanceId); + if (calendatInstance == null) { throw new CalendarInstanceNotFoundException(CalendarInstanceId); } + return calendatInstance; + } + + public void save(final CalendarInstance calendatInstance) { + this.repository.save(calendatInstance); + } + + public void delete(final CalendarInstance calendatInstance) { + this.repository.delete(calendatInstance); + } + + public void saveAndFlush(final CalendarInstance calendatInstance) { + this.repository.saveAndFlush(calendatInstance); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/CalendarInstanceNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/CalendarInstanceNotFoundException.java new file mode 100644 index 000000000..13042f555 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/CalendarInstanceNotFoundException.java @@ -0,0 +1,22 @@ +/** + * 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.calendar.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +/** + * A {@link RuntimeException} thrown when CalendarInstance resources are not found. + */ +public class CalendarInstanceNotFoundException extends AbstractPlatformResourceNotFoundException { + + public CalendarInstanceNotFoundException(final Long id) { + super("error.msg.calendar.instance.id.invalid", "Calendar Instance with identifier " + id + " does not exist", id); + } + + public CalendarInstanceNotFoundException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) { + super("error.msg.calendar.instance." + postFix, defaultUserMessage, defaultUserMessageArgs); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java index 81b02a433..4a8b719e4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.calendar.service; +import org.joda.time.LocalDate; import org.mifosplatform.portfolio.calendar.data.CalendarData; import java.util.Collection; @@ -23,9 +24,13 @@ public interface CalendarReadPlatformService { CalendarData retrieveNewCalendarDetails(); CalendarData generateRecurringDate(final CalendarData calendarData); + + CalendarData generateRecurringDate(final CalendarData calendarData, final LocalDate tillDate); Collection generateRecurringDates(final Collection calendarsData); CalendarData retrieveLoanCalendar(final Long loanId); + + CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java index 9927279ea..353c842f5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java @@ -18,6 +18,7 @@ import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -71,7 +72,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ if (remindById != null && remindById != 0) remindBy = CalendarEnumerations.calendarRemindBy(remindById); final Integer firstReminder = rs.getInt("firstReminder"); final Integer secondReminder = rs.getInt("secondReminder"); - final String humanReadable = CalendarHelper.getRRuleReadable(startDate, recurrence); + final String humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence); final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate"); final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate"); @@ -113,13 +114,27 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? order by c.start_date "; result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId }); } else if (!calendarTypeOptions.isEmpty()) { - String sqlCalendarTypeOptions = getSqlCalendarTypeOptionsInString(calendarTypeOptions); + String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions); sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? and c.calendar_type_enum in ( " + sqlCalendarTypeOptions + " ) order by c.start_date "; result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId }); } return result; } + + @Override + public CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId) { + final CalendarDataMapper rm = new CalendarDataMapper(); + + String sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? and calendar_type_enum = ? order by c.start_date "; + List result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId, CalendarType.COLLECTION.getValue() }); + + if(!result.isEmpty() && result.size() > 0){ + return result.get(0); + } + + return null; + } @Override public Collection retrieveParentCalendarsByEntity(final Long entityId, final Integer entityTypeId, @@ -137,7 +152,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ sql = rm.schema() + " " + parentHeirarchyCondition + " and ci.entity_type_enum = ? order by c.start_date "; result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, CalendarEntityType.CENTERS.getValue() }); } else { - String sqlCalendarTypeOptions = getSqlCalendarTypeOptionsInString(calendarTypeOptions); + String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions); sql = rm.schema() + " " + parentHeirarchyCondition + " and ci.entity_type_enum = ? and c.calendar_type_enum in (" + sqlCalendarTypeOptions + ") order by c.start_date "; result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, CalendarEntityType.CENTERS.getValue() }); @@ -162,6 +177,11 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ @Override public CalendarData generateRecurringDate(final CalendarData calendarData) { + return generateRecurringDate(calendarData, null); + } + + @Override + public CalendarData generateRecurringDate(CalendarData calendarData, LocalDate tillDate) { if (!calendarData.isRepeating()) return calendarData; final String rrule = calendarData.getRecurrence(); final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); @@ -172,13 +192,22 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ startDate = currentDate.minusYears(1); } - if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) { + if(tillDate != null){ + if(endDate != null){ + if(endDate.isAfter(tillDate)){ + endDate = tillDate;//to retrieve meeting dates till specified date (tillDate) + } + }else{ + endDate = tillDate;//end date is null then fetch meeting dates tillDate + } + }else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) { endDate = currentDate.plusYears(1); } - - final Collection recurringDates = CalendarHelper.getRecurringDates(rrule, seedDate, startDate, endDate, -1); - final Collection nextTenRecurringDates = CalendarHelper.getRecurringDates(rrule, seedDate, endDate); + + final Collection recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, startDate, endDate, -1); + final Collection nextTenRecurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, endDate); return new CalendarData(calendarData, recurringDates, nextTenRecurringDates); + } @Override @@ -238,21 +267,5 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ return conditionSql; } - - /** - * function returns a comma separated list of calendar_type_enum values ex. - * 1,2,3,4 - * - * @param calendarTypeOptions - * @return - */ - public String getSqlCalendarTypeOptionsInString(final List calendarTypeOptions) { - String sqlCalendarTypeOptions = ""; - int size = calendarTypeOptions.size(); - for (int i = 0; i < size - 1; i++) { - sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ","; - } - sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString(); - return sqlCalendarTypeOptions; - } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarUtils.java similarity index 85% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarUtils.java index df8a4634c..a02cc61fa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarUtils.java @@ -11,6 +11,8 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; +import java.util.StringTokenizer; import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.DateList; @@ -32,10 +34,10 @@ import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; -public class CalendarHelper { +public class CalendarUtils { public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) { - final Recur recur = CalendarHelper.getICalRecur(recurringRule); + final Recur recur = CalendarUtils.getICalRecur(recurringRule); if (recur == null) { return null; } return getNextRecurringDate(recur, seedDate, startDate); } @@ -82,7 +84,7 @@ public class CalendarHelper { public static Collection getRecurringDates(final String recurringRule, final LocalDate seedDate, final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount) { - final Recur recur = CalendarHelper.getICalRecur(recurringRule); + final Recur recur = CalendarUtils.getICalRecur(recurringRule); return getRecurringDates(recur, seedDate, periodStartDate, periodEndDate, maxCount); @@ -210,7 +212,7 @@ public class CalendarHelper { public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date){ - final Recur recur = CalendarHelper.getICalRecur(recurringRule); + final Recur recur = CalendarUtils.getICalRecur(recurringRule); if (recur == null) { return false; } return isValidRecurringDate(recur, seedDate, date); @@ -248,7 +250,7 @@ public class CalendarHelper { } public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule){ - final Recur recur = CalendarHelper.getICalRecur(recurringRule); + final Recur recur = CalendarUtils.getICalRecur(recurringRule); PeriodFrequencyType meetingFrequencyType = PeriodFrequencyType.INVALID; if(recur.getFrequency().equals(Recur.DAILY)){ meetingFrequencyType = PeriodFrequencyType.DAYS; @@ -277,12 +279,12 @@ public class CalendarHelper { } public static int getInterval(final String recurringRule){ - final Recur recur = CalendarHelper.getICalRecur(recurringRule); + final Recur recur = CalendarUtils.getICalRecur(recurringRule); return recur.getInterval(); } public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate, final Integer loanRepaymentInterval, final String frequency){ - final Recur recur = CalendarHelper.getICalRecur(calendar.getRecurrence()); + final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence()); if (recur == null) { return null; } LocalDate startDate = disbursementDate; final LocalDate seedDate = calendar.getStartDateLocalDate(); @@ -310,7 +312,7 @@ public class CalendarHelper { } public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays){ - final Recur recur = CalendarHelper.getICalRecur(recurringRule); + final Recur recur = CalendarUtils.getICalRecur(recurringRule); if (recur == null) { return null; } if(isValidRecurringDate(recur, seedDate, oldRepaymentDate)){ return oldRepaymentDate; @@ -320,7 +322,7 @@ public class CalendarHelper { public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays){ - final Recur recur = CalendarHelper.getICalRecur(recurringRule); + final Recur recur = CalendarUtils.getICalRecur(recurringRule); if (recur == null) { return null; } LocalDate tmpDate = repaymentDate; if(isValidRecurringDate(recur, seedDate, repaymentDate)){ @@ -364,4 +366,53 @@ public class CalendarHelper { if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) { return false; } return (oldRecur.getInterval() == newRecur.getInterval()); } + + public static List createIntegerListFromQueryParameter(String calendarTypeQuery) { + List calendarTypeOptions = new ArrayList(); + // adding all calendar Types if query parameter is "all" + if(calendarTypeQuery.equalsIgnoreCase("all")){ + calendarTypeOptions.add(1); + calendarTypeOptions.add(2); + calendarTypeOptions.add(3); + calendarTypeOptions.add(4); + return calendarTypeOptions; + } + // creating a list of calendar type options from the comma separated query parameter. + List calendarTypeOptionsInQuery = new ArrayList(); + StringTokenizer st = new StringTokenizer(calendarTypeQuery, ","); + while (st.hasMoreElements()) { + calendarTypeOptionsInQuery.add(st.nextElement().toString()); + } + + for(String calType : calendarTypeOptionsInQuery){ + if(calType.equalsIgnoreCase("collection")) { + calendarTypeOptions.add(1); + } else if(calType.equalsIgnoreCase("training")) { + calendarTypeOptions.add(2); + } else if(calType.equalsIgnoreCase("audit")) { + calendarTypeOptions.add(3); + } else if(calType.equalsIgnoreCase("general")) { + calendarTypeOptions.add(4); + } + } + + return calendarTypeOptions; + } + + /** + * function returns a comma separated list of calendar_type_enum values ex. + * 1,2,3,4 + * + * @param calendarTypeOptions + * @return + */ + public static String getSqlCalendarTypeOptionsInString(final List calendarTypeOptions) { + String sqlCalendarTypeOptions = ""; + int size = calendarTypeOptions.size(); + for (int i = 0; i < size - 1; i++) { + sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ","; + } + sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString(); + return sqlCalendarTypeOptions; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java index ad59d6e4b..fe70c2e74 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java @@ -128,7 +128,7 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId); if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); } - final CalendarInstance calendarInstanceForUpdate = this.calendarInstanceRepository.findByCalendarAndEntityIdAndEntityTypeId(calendarId, entityId, entityTypeId); + final CalendarInstance calendarInstanceForUpdate = this.calendarInstanceRepository.findByCalendarIdAndEntityIdAndEntityTypeId(calendarId, entityId, entityTypeId); this.calendarInstanceRepository.saveAndFlush(calendarInstanceForUpdate); return new CommandProcessingResultBuilder() // .withCommandId(null) // diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java index cfd3514ab..12ba2a31c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java @@ -229,7 +229,7 @@ public class Charge extends AbstractPersistable { EnumOptionData chargeAppliesTo = ChargeEnumerations.chargeAppliesTo(this.chargeAppliesTo); EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(this.chargeCalculation); - CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, null, null); + CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null); return ChargeData.instance(this.getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, penalty, active); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java index c18d08db2..8a20c0a55 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java @@ -111,7 +111,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.LOAN.getValue() }); } - + @Override public Collection retrieveLoanApplicablePenalties() { this.context.authenticatedUser(); @@ -129,7 +129,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService return "c.id as id, c.name as name, c.amount as amount, c.currency_code as currencyCode, " + "c.charge_applies_to_enum as chargeAppliesTo, c.charge_time_enum as chargeTime, " + "c.charge_calculation_enum as chargeCalculation, c.is_penalty as penalty, c.is_active as active, oc.name as currencyName, " - + "oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " + + "oc.decimal_places as currencyDecimalPlaces,oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " + "oc.internationalized_name_code as currencyNameCode from m_charge c " + "join m_organisation_currency oc on c.currency_code = oc.code"; } @@ -149,9 +149,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, currencyDisplaySymbol, - currencyNameCode); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); final int chargeAppliesTo = rs.getInt("chargeAppliesTo"); final EnumOptionData chargeAppliesToType = ChargeEnumerations.chargeAppliesTo(chargeAppliesTo); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java index 9309e030d..4fc537a27 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java @@ -34,7 +34,8 @@ import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSeriali import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer; import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; -import org.mifosplatform.portfolio.client.data.ClientAccountSummaryCollectionData; +import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData; +import org.mifosplatform.portfolio.accountdetails.service.AccountDetailsReadPlatformService; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.group.service.SearchParameters; @@ -50,22 +51,25 @@ public class ClientsApiResource { private final PlatformSecurityContext context; private final ClientReadPlatformService clientReadPlatformService; private final ToApiJsonSerializer toApiJsonSerializer; - private final ToApiJsonSerializer clientAccountSummaryToApiJsonSerializer; + private final ToApiJsonSerializer clientAccountSummaryToApiJsonSerializer; private final ApiRequestParameterHelper apiRequestParameterHelper; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final AccountDetailsReadPlatformService accountDetailsReadPlatformService; @Autowired public ClientsApiResource(final PlatformSecurityContext context, final ClientReadPlatformService readPlatformService, final ToApiJsonSerializer toApiJsonSerializer, - final ToApiJsonSerializer clientAccountSummaryToApiJsonSerializer, + final ToApiJsonSerializer clientAccountSummaryToApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, - final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + final AccountDetailsReadPlatformService accountDetailsReadPlatformService) { this.context = context; this.clientReadPlatformService = readPlatformService; this.toApiJsonSerializer = toApiJsonSerializer; this.clientAccountSummaryToApiJsonSerializer = clientAccountSummaryToApiJsonSerializer; this.apiRequestParameterHelper = apiRequestParameterHelper; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.accountDetailsReadPlatformService = accountDetailsReadPlatformService; } @GET @@ -203,6 +207,9 @@ public class ClientsApiResource { } else if (is(commandParam, "close")) { commandRequest = builder.closeClient(clientId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + }else if (is(commandParam, "transfer")) { + commandRequest = builder.transferClient(clientId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", @@ -223,7 +230,7 @@ public class ClientsApiResource { context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME); - final ClientAccountSummaryCollectionData clientAccount = this.clientReadPlatformService.retrieveClientAccountDetails(clientId); + final AccountSummaryCollectionData clientAccount = this.accountDetailsReadPlatformService.retrieveClientAccountDetails(clientId); final Set CLIENT_ACCOUNTS_DATA_PARAMETERS = new HashSet(Arrays.asList("loanAccounts", "savingsAccounts")); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientAccountSummaryCollectionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientAccountSummaryCollectionData.java deleted file mode 100644 index 239b96947..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientAccountSummaryCollectionData.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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.client.data; - -import java.util.Collection; - -/** - * Immutable data object representing a summary of a clients various accounts. - */ -public class ClientAccountSummaryCollectionData { - - @SuppressWarnings("unused") - private final Collection loanAccounts; - @SuppressWarnings("unused") - private final Collection savingsAccounts; - - public ClientAccountSummaryCollectionData(final Collection loanAccounts, - final Collection savingsAccounts) { - this.loanAccounts = defaultIfEmpty(loanAccounts); - this.savingsAccounts = savingsAccounts; - } - - private Collection defaultIfEmpty(final Collection collection) { - Collection returnCollection = null; - if (collection != null && !collection.isEmpty()) { - returnCollection = collection; - } - return returnCollection; - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientData.java index 1fc10807d..41f0564e0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientData.java @@ -23,7 +23,6 @@ import org.mifosplatform.portfolio.group.data.GroupGeneralData; */ final public class ClientData implements Comparable { - private final Long id; private final String accountNo; private final String externalId; @@ -49,16 +48,17 @@ final public class ClientData implements Comparable { // associations private final Collection groups; - + // template private final Collection officeOptions; private final Collection staffOptions; @SuppressWarnings("unused") private final Collection closureReasons; - - public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection officeOptions, + + public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection officeOptions, final Collection staffOptions, final Collection closureReasons) { - return new ClientData(null, null, officeId, null, null, null, null, null, null, null, null, joinedDate, null, null, null, officeOptions, null, staffOptions, closureReasons); + return new ClientData(null, null, officeId, null, null, null, null, null, null, null, null, joinedDate, null, null, null, + officeOptions, null, staffOptions, closureReasons); } public static ClientData templateOnTop(final ClientData clientData, final ClientData templateData) { @@ -81,11 +81,12 @@ final public class ClientData implements Comparable { final String officeName) { return new ClientData(accountNo, status, officeId, officeName, id, firstname, middlename, lastname, fullname, displayName, null, - null, null, null, null, null, null,null, null); + null, null, null, null, null, null, null, null); } public static ClientData lookup(final Long id, final String displayName, final Long officeId, final String officeName) { - return new ClientData(null, null, officeId, officeName, id, null, null, null, null, displayName, null, null, null, null, null, null, null, null, null); + return new ClientData(null, null, officeId, officeName, id, null, null, null, null, displayName, null, null, null, null, null, + null, null, null, null); } public static ClientData instance(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName, @@ -99,8 +100,8 @@ final public class ClientData implements Comparable { private ClientData(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName, final Long id, final String firstname, final String middlename, final String lastname, final String fullname, final String displayName, final String externalId, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName, - final Collection allowedOffices, final Collection groups, final Collection staffOptions, - final Collection closureReasons) { + final Collection allowedOffices, final Collection groups, + final Collection staffOptions, final Collection closureReasons) { this.accountNo = accountNo; this.status = status; if (status != null) { @@ -122,7 +123,7 @@ final public class ClientData implements Comparable { if (imageId != null) { this.imagePresent = Boolean.TRUE; } else { - this.imagePresent = Boolean.FALSE; + this.imagePresent = null; } this.staffId = staffId; this.staffName = staffName; @@ -189,7 +190,6 @@ final public class ClientData implements Comparable { .toHashCode(); } - // TODO - kw - look into removing usage of the getters below public String getExternalId() { return this.externalId; } @@ -205,5 +205,4 @@ final public class ClientData implements Comparable { public LocalDate getActivationDate() { return this.activationDate; } - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java index c1c958095..aaf9fca4b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java @@ -96,11 +96,9 @@ public final class Client extends AbstractPersistable { @Transient private boolean accountNumberRequiresAutoGeneration = false; - @SuppressWarnings("unused") @Column(name = "closure_reason_cv_id", nullable = true) private Long closureReasonId; - @SuppressWarnings("unused") @Column(name = "closedon_date", nullable = true) @Temporal(TemporalType.DATE) private Date closureDate; @@ -431,6 +429,10 @@ public final class Client extends AbstractPersistable { public Office getOffice() { return this.office; } + + public void updateOffice(final Office office) { + this.office = office; + } private Long staffId() { Long staffId = null; @@ -455,6 +457,10 @@ public final class Client extends AbstractPersistable { public void assignStaff(final Staff staff) { this.staff = staff; } + + public Set getGroups(){ + return this.groups; + } public void close(final Long closureReasonId, final Date closureDate) { @@ -464,10 +470,19 @@ public final class Client extends AbstractPersistable { } public Integer getStatus() { - return status; + return this.status; } public boolean isActivatedAfter(final LocalDate submittedOn) { return getActivationLocalDate().isAfter(submittedOn); } + + public boolean isChildOfGroup(final Long groupId) { + if (groupId != null && this.groups != null && !this.groups.isEmpty()) { + for (Group group : this.groups) { + if (group.getId().equals(groupId)) return true; + } + } + return false; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientIdentifier.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientIdentifier.java index 2b9bcc29d..276e1e4bf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientIdentifier.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientIdentifier.java @@ -23,78 +23,89 @@ import org.mifosplatform.useradministration.domain.AppUser; @Entity @Table(name = "m_client_identifier", uniqueConstraints = { - @UniqueConstraint(columnNames = { "document_type_id", "document_key" }, name = "unique_identifier_key"), - @UniqueConstraint(columnNames = { "client_id", "document_type_id" }, name = "unique_client_identifier") }) + @UniqueConstraint(columnNames = { "document_type_id", "document_key" }, name = "unique_identifier_key"), + @UniqueConstraint(columnNames = { "client_id", "document_type_id" }, name = "unique_client_identifier") }) public class ClientIdentifier extends AbstractAuditableCustom { - @SuppressWarnings("unused") - @ManyToOne - @JoinColumn(name = "client_id", nullable = false) - private Client client; + @ManyToOne + @JoinColumn(name = "client_id", nullable = false) + private Client client; - @ManyToOne - @JoinColumn(name = "document_type_id", nullable = false) - private CodeValue documentType; + @ManyToOne + @JoinColumn(name = "document_type_id", nullable = false) + private CodeValue documentType; - @Column(name = "document_key", length = 1000) - private String documentKey; + @Column(name = "document_key", length = 1000) + private String documentKey; - @Column(name = "description", length = 1000) - private String description; + @Column(name = "description", length = 1000) + private String description; - public static ClientIdentifier fromJson(final Client client, final CodeValue documentType, final JsonCommand command) { - final String documentKey = command.stringValueOfParameterNamed("documentKey"); - final String description = command.stringValueOfParameterNamed("description"); - return new ClientIdentifier(client, documentType, documentKey, description); - } + public static ClientIdentifier fromJson(final Client client, + final CodeValue documentType, final JsonCommand command) { + final String documentKey = command + .stringValueOfParameterNamed("documentKey"); + final String description = command + .stringValueOfParameterNamed("description"); + return new ClientIdentifier(client, documentType, documentKey, + description); + } - protected ClientIdentifier() { - // - } + protected ClientIdentifier() { + // + } - private ClientIdentifier(final Client client, final CodeValue documentType, final String documentKey, final String description) { - this.client = client; - this.documentType = documentType; - this.documentKey = StringUtils.defaultIfEmpty(documentKey, null); - this.description = StringUtils.defaultIfEmpty(description, null); - } + private ClientIdentifier(final Client client, final CodeValue documentType, + final String documentKey, final String description) { + this.client = client; + this.documentType = documentType; + this.documentKey = StringUtils.defaultIfEmpty(documentKey, null); + this.description = StringUtils.defaultIfEmpty(description, null); + } - public void update(final CodeValue documentType) { - this.documentType = documentType; - } + public void update(final CodeValue documentType) { + this.documentType = documentType; + } - public Map update(final JsonCommand command) { + public Map update(final JsonCommand command) { - final Map actualChanges = new LinkedHashMap(7); + final Map actualChanges = new LinkedHashMap( + 7); - final String documentTypeIdParamName = "documentTypeId"; - if (command.isChangeInLongParameterNamed(documentTypeIdParamName, this.documentType.getId())) { - final Long newValue = command.longValueOfParameterNamed(documentTypeIdParamName); - actualChanges.put(documentTypeIdParamName, newValue); - } + final String documentTypeIdParamName = "documentTypeId"; + if (command.isChangeInLongParameterNamed(documentTypeIdParamName, + this.documentType.getId())) { + final Long newValue = command + .longValueOfParameterNamed(documentTypeIdParamName); + actualChanges.put(documentTypeIdParamName, newValue); + } - final String documentKeyParamName = "documentKey"; - if (command.isChangeInStringParameterNamed(documentKeyParamName, this.documentKey)) { - final String newValue = command.stringValueOfParameterNamed(documentKeyParamName); - actualChanges.put(documentKeyParamName, newValue); - this.documentKey = StringUtils.defaultIfEmpty(newValue, null); - } + final String documentKeyParamName = "documentKey"; + if (command.isChangeInStringParameterNamed(documentKeyParamName, + this.documentKey)) { + final String newValue = command + .stringValueOfParameterNamed(documentKeyParamName); + actualChanges.put(documentKeyParamName, newValue); + this.documentKey = StringUtils.defaultIfEmpty(newValue, null); + } - final String descriptionParamName = "description"; - if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) { - final String newValue = command.stringValueOfParameterNamed(descriptionParamName); - actualChanges.put(descriptionParamName, newValue); - this.description = StringUtils.defaultIfEmpty(newValue, null); - } + final String descriptionParamName = "description"; + if (command.isChangeInStringParameterNamed(descriptionParamName, + this.description)) { + final String newValue = command + .stringValueOfParameterNamed(descriptionParamName); + actualChanges.put(descriptionParamName, newValue); + this.description = StringUtils.defaultIfEmpty(newValue, null); + } - return actualChanges; - } + return actualChanges; + } - public String documentKey() { - return this.documentKey; - } + public String documentKey() { + return this.documentKey; + } - public Long documentTypeId() { - return this.documentType.getId(); - } + public Long documentTypeId() { + return this.documentType.getId(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientRepository.java index 2b30304dd..1b8af41fa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientRepository.java @@ -9,5 +9,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface ClientRepository extends JpaRepository, JpaSpecificationExecutor { - // no added behaviour + // no added behaviour } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientHasBeenClosedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientHasBeenClosedException.java new file mode 100644 index 000000000..465421c46 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientHasBeenClosedException.java @@ -0,0 +1,11 @@ +package org.mifosplatform.portfolio.client.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class ClientHasBeenClosedException extends AbstractPlatformDomainRuleException { + + public ClientHasBeenClosedException(final Long clientId) { + super("error.msg.client.closed", "Client with identifier " + clientId + " has already been closed"); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/serialization/ClientIdentifierCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/serialization/ClientIdentifierCommandFromApiJsonDeserializer.java index 872bdcb63..2bfc6d928 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/serialization/ClientIdentifierCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/serialization/ClientIdentifierCommandFromApiJsonDeserializer.java @@ -16,7 +16,6 @@ import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer; import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.client.command.ClientCommand; import org.mifosplatform.portfolio.client.command.ClientIdentifierCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -25,7 +24,7 @@ import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; /** - * Implementation of {@link FromApiJsonDeserializer} for {@link ClientCommand} + * Implementation of {@link FromApiJsonDeserializer} for {@link ClientIdentifierCommandFromApiJsonDeserializer} * 's. */ @Component diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformService.java index 29b15bb45..2d769e9c3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformService.java @@ -8,8 +8,6 @@ package org.mifosplatform.portfolio.client.service; import java.util.Collection; import org.mifosplatform.infrastructure.core.service.Page; -import org.mifosplatform.portfolio.client.data.ClientAccountSummaryCollectionData; -import org.mifosplatform.portfolio.client.data.ClientLoanAccountSummaryData; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.group.service.SearchParameters; @@ -25,15 +23,13 @@ public interface ClientReadPlatformService { Collection retrieveAllForLookupByOfficeId(Long officeId); - ClientAccountSummaryCollectionData retrieveClientAccountDetails(Long clientId); - - Collection retrieveClientLoanAccountsByLoanOfficerId(Long clientId, Long loanOfficerId); - ClientData retrieveClientByIdentifier(Long identifierTypeId, String identifierKey); Collection retrieveClientMembersOfGroup(Long groupId); - + Collection retrieveActiveClientMembersOfGroup(Long groupId); + + Collection retrieveActiveClientMembersOfCenter(final Long centerId); ClientData retrieveAllClosureReasons(String clientClosureReason); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java index 0492bba20..8eb05e30a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java @@ -5,7 +5,6 @@ */ package org.mifosplatform.portfolio.client.service; -import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -23,24 +22,16 @@ import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.core.service.PaginationHelper; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; -import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.office.data.OfficeData; import org.mifosplatform.organisation.office.service.OfficeReadPlatformService; import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.organisation.staff.service.StaffReadPlatformService; -import org.mifosplatform.portfolio.client.data.ClientAccountSummaryCollectionData; -import org.mifosplatform.portfolio.client.data.ClientLoanAccountSummaryData; import org.mifosplatform.portfolio.client.data.ClientData; -import org.mifosplatform.portfolio.client.data.ClientSavingsAccountSummaryData; import org.mifosplatform.portfolio.client.domain.ClientEnumerations; import org.mifosplatform.portfolio.client.domain.ClientStatus; import org.mifosplatform.portfolio.client.exception.ClientNotFoundException; import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.service.SearchParameters; -import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData; -import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; -import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; -import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -63,7 +54,6 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService private final ClientLookupMapper lookupMapper = new ClientLookupMapper(); private final ClientMembersOfGroupMapper membersOfGroupMapper = new ClientMembersOfGroupMapper(); private final ParentGroupsMapper clientGroupsMapper = new ParentGroupsMapper(); - private final ClientSavingsAccountSummaryDataMapper savingsAccountSummaryDataMapper = new ClientSavingsAccountSummaryDataMapper(); @Autowired public ClientReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, @@ -255,7 +245,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService return this.jdbcTemplate.query(sql, this.membersOfGroupMapper, new Object[] { hierarchySearchString, groupId, ClientStatus.ACTIVE.getValue() }); } - + private static final class ClientMembersOfGroupMapper implements RowMapper { private final String schema; @@ -308,6 +298,20 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService } } + @Override + public Collection retrieveActiveClientMembersOfCenter(final Long centerId) { + + final AppUser currentUser = context.authenticatedUser(); + final String hierarchy = currentUser.getOffice().getHierarchy(); + final String hierarchySearchString = hierarchy + "%"; + + final String sql = "select " + this.membersOfGroupMapper.schema() + + " left join m_group g on pgc.group_id=g.id where o.hierarchy like ? and g.parent_id = ? and c.status_enum = ? group by c.id"; + + return this.jdbcTemplate.query(sql, this.membersOfGroupMapper, + new Object[] { hierarchySearchString, centerId, ClientStatus.ACTIVE.getValue() }); + } + private static final class ClientMapper implements RowMapper { private final String schema; @@ -408,125 +412,6 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService } } - @Override - public ClientAccountSummaryCollectionData retrieveClientAccountDetails(final Long clientId) { - - try { - // Check if client exists - retrieveOne(clientId); - - ClientLoanAccountSummaryDataMapper rm = new ClientLoanAccountSummaryDataMapper(); - - String sql = "select " + rm.loanAccountSummarySchema() + " where l.client_id = ?"; - - List allLoanAccounts = this.jdbcTemplate.query(sql, rm, new Object[] { clientId }); - - final String savingsSql = "select " + this.savingsAccountSummaryDataMapper.schema() - + " where sa.client_id = ? order by sa.status_enum ASC, sa.account_no ASC"; - - final List allSavingsAccounts = this.jdbcTemplate.query(savingsSql, - savingsAccountSummaryDataMapper, new Object[] { clientId }); - - return new ClientAccountSummaryCollectionData(allLoanAccounts, allSavingsAccounts); - } catch (EmptyResultDataAccessException e) { - throw new ClientNotFoundException(clientId); - } - } - - @Override - public Collection retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId) { - - this.context.authenticatedUser(); - - // Check if client exists - retrieveOne(clientId); - - ClientLoanAccountSummaryDataMapper rm = new ClientLoanAccountSummaryDataMapper(); - - String sql = "select " + rm.loanAccountSummarySchema() + " where l.client_id = ? and l.loan_officer_id = ?"; - - List loanAccounts = this.jdbcTemplate.query(sql, rm, new Object[] { clientId, loanOfficerId }); - - return loanAccounts; - } - - private static final class ClientSavingsAccountSummaryDataMapper implements RowMapper { - - final String schemaSql; - - public ClientSavingsAccountSummaryDataMapper() { - StringBuilder accountsSummary = new StringBuilder(); - accountsSummary.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, sa.status_enum as statusEnum, "); - accountsSummary.append("sa.account_balance_derived as accountBalance, "); - accountsSummary.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, "); - accountsSummary.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - accountsSummary.append("curr.display_symbol as currencyDisplaySymbol, "); - accountsSummary.append("sa.product_id as productId, p.name as productName "); - accountsSummary.append("from m_savings_account sa "); - accountsSummary.append("join m_savings_product as p on p.id = sa.product_id "); - accountsSummary.append("join m_currency curr on curr.code = sa.currency_code "); - - this.schemaSql = accountsSummary.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public ClientSavingsAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = JdbcSupport.getLong(rs, "id"); - final String accountNo = rs.getString("accountNo"); - final String externalId = rs.getString("externalId"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - final String productName = rs.getString("productName"); - final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); - final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance"); - final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); - - return new ClientSavingsAccountSummaryData(id, accountNo, externalId, productId, productName, status, currency, accountBalance); - } - } - - private static final class ClientLoanAccountSummaryDataMapper implements RowMapper { - - public String loanAccountSummarySchema() { - - StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,"); - accountsSummary.append("l.product_id as productId, lp.name as productName,") - .append("l.loan_status_id as statusId, l.loan_type_enum as loanType, ").append("l.loan_product_counter as loanCycle ") - .append(" from m_loan l ").append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id"); - - return accountsSummary.toString(); - } - - @Override - public ClientLoanAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = JdbcSupport.getLong(rs, "id"); - final String accountNo = rs.getString("accountNo"); - final String externalId = rs.getString("externalId"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - final String loanProductName = rs.getString("productName"); - final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId"); - final LoanStatusEnumData loanStatus = LoanEnumerations.status(loanStatusId); - final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType"); - final EnumOptionData loanType = LoanEnumerations.loanType(loanTypeId); - final Integer loanCycle = JdbcSupport.getInteger(rs, "loanCycle"); - - return new ClientLoanAccountSummaryData(id, accountNo, externalId, productId, loanProductName, loanStatus, loanType, loanCycle); - } - } - @Override public ClientData retrieveClientByIdentifier(final Long identifierTypeId, final String identifierKey) { try { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java index bd7270965..a97f2bbec 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java @@ -306,6 +306,11 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP final Long staffId = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName); if (staffId != null) { staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, clientForUpdate.getOffice().getHierarchy()); + /** + * TODO Vishwas: We maintain history of chage of loan officer w.r.t + * loan in a history table, should we do the same for a client? + * Especially useful when the change happens due to a transfer etc + **/ clientForUpdate.assignStaff(staff); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collateral/domain/LoanCollateral.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collateral/domain/LoanCollateral.java index 5ad056a76..8e6eb8edf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collateral/domain/LoanCollateral.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collateral/domain/LoanCollateral.java @@ -30,7 +30,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_loan_collateral") public class LoanCollateral extends AbstractPersistable { - @SuppressWarnings("unused") @ManyToOne(optional = false) @JoinColumn(name = "loan_id", nullable = false) private Loan loan; @@ -105,10 +104,10 @@ public class LoanCollateral extends AbstractPersistable { public CollateralData toData() { final CodeValueData typeData = this.type.toData(); - return CollateralData.instance(this.getId(), typeData, this.value, this.description, null); + return CollateralData.instance(getId(), typeData, this.value, this.description, null); } - public void setCollateralType(CodeValue type) { + public void setCollateralType(final CodeValue type) { this.type = type; } @@ -117,9 +116,9 @@ public class LoanCollateral extends AbstractPersistable { if (obj == null) { return false; } if (obj == this) { return true; } if (obj.getClass() != getClass()) { return false; } - LoanCollateral rhs = (LoanCollateral) obj; + final LoanCollateral rhs = (LoanCollateral) obj; return new EqualsBuilder().appendSuper(super.equals(obj)) // - .append(this.getId(), rhs.getId()) // + .append(getId(), rhs.getId()) // .append(this.type.getId(), rhs.type.getId()) // .append(this.description, rhs.description) // .append(this.value, this.value)// @@ -129,7 +128,7 @@ public class LoanCollateral extends AbstractPersistable { @Override public int hashCode() { return new HashCodeBuilder(3, 5) // - .append(this.getId()) // + .append(getId()) // .append(this.type.getId()) // .append(this.description) // .append(this.value)// diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collateral/service/CollateralReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collateral/service/CollateralReadPlatformServiceImpl.java index 53703d30d..b6caef1dd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collateral/service/CollateralReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collateral/service/CollateralReadPlatformServiceImpl.java @@ -45,7 +45,7 @@ public class CollateralReadPlatformServiceImpl implements CollateralReadPlatform private StringBuilder sqlBuilder = new StringBuilder( "lc.id as id, lc.description as description, lc.value as value, cv.id as typeId, cv.code_value as typeName, oc.code as currencyCode, ") - .append(" oc.name as currencyName,oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, oc.internationalized_name_code as currencyNameCode") + .append(" oc.name as currencyName,oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, oc.internationalized_name_code as currencyNameCode") .append(" FROM m_loan_collateral lc") // .append(" JOIN m_code_value cv on lc.type_cv_id = cv.id")// .append(" JOIN m_loan loan on lc.loan_id = loan.id")// @@ -71,9 +71,10 @@ public class CollateralReadPlatformServiceImpl implements CollateralReadPlatform final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, currencyDisplaySymbol, - currencyNameCode); + final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); return CollateralData.instance(id, type, value, description, currencyData); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java index 60e57cf00..64d7f8386 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java @@ -52,7 +52,8 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe @Autowired public CollectionSheetReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, - final CenterReadPlatformService centerReadPlatformService, final GroupReadPlatformService groupReadPlatformService, final CollectionSheetGenerateCommandFromApiJsonDeserializer collectionSheetGenerateCommandFromApiJsonDeserializer) { + final CenterReadPlatformService centerReadPlatformService, final GroupReadPlatformService groupReadPlatformService, + final CollectionSheetGenerateCommandFromApiJsonDeserializer collectionSheetGenerateCommandFromApiJsonDeserializer) { this.context = context; this.centerReadPlatformService = centerReadPlatformService; this.namedParameterjdbcTemplate = new NamedParameterJdbcTemplate(dataSource); @@ -69,9 +70,10 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe final CenterData center = this.centerReadPlatformService.retrieveOne(centerId); final String groupHierarchy = center.getHierarchy() + "%"; - - Collection collectionSheetFlatDatas = this.retriveJLGCollectionSheet(groupHierarchy, officeHierarchy, dueDate); - + + Collection collectionSheetFlatDatas = this.retriveJLGCollectionSheet(groupHierarchy, officeHierarchy, + dueDate); + final JLGCollectionSheetData jlgCollectionSheetData = buildJLGCollectionSheet(dueDate, collectionSheetFlatDatas); return jlgCollectionSheetData; @@ -83,7 +85,8 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe * Groups >> Clients >> Loans. */ @SuppressWarnings("null") - private JLGCollectionSheetData buildJLGCollectionSheet(final LocalDate dueDate, final Collection jlgCollectionSheetFlatData) { + private JLGCollectionSheetData buildJLGCollectionSheet(final LocalDate dueDate, + final Collection jlgCollectionSheetFlatData) { boolean firstTime = true; Long prevGroupId = null; @@ -101,8 +104,10 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe for (final JLGCollectionSheetFlatData collectionSheetFlatData : jlgCollectionSheetFlatData) { - loanProducts.add(LoanProductData.lookupWithCurrency(collectionSheetFlatData.getProductId(), - collectionSheetFlatData.getProductShortName(), collectionSheetFlatData.getCurrency())); + if (collectionSheetFlatData.getProductId() != null) { + loanProducts.add(LoanProductData.lookupWithCurrency(collectionSheetFlatData.getProductId(), + collectionSheetFlatData.getProductShortName(), collectionSheetFlatData.getCurrency())); + } corrCollectioSheetFlatData = collectionSheetFlatData; if (firstTime || collectionSheetFlatData.getGroupId().equals(prevGroupId)) { @@ -195,7 +200,7 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe + "ln.loan_status_id As accountStatusId, " + "pl.name As productShortName, " + "ln.product_id As productId, " - + "ln.currency_code as currencyCode, ln.currency_digits as currencyDigits, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, " + + "ln.currency_code as currencyCode, ln.currency_digits as currencyDigits, ln.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, " + "if(ln.loan_status_id = 200 , ln.principal_amount , null) As disbursementAmount, " + "sum(ifnull(if(ln.loan_status_id = 300, ls.principal_amount, 0.0), 0.0) - ifnull(if(ln.loan_status_id = 300, ls.principal_completed_derived, 0.0), 0.0)) As principalDue, " + "ln.principal_repaid_derived As principalPaid, " @@ -204,17 +209,20 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe + "sum(if(ln.loan_status_id = 300, lc.amount_outstanding_derived, 0.0)) as chargesDue " + "FROM m_group gp " + "LEFT JOIN m_office of ON of.id = gp.office_id AND of.hierarchy like :officeHierarchy " - //+ "LEFT JOIN m_office of ON of.id = gp.office_id " + // + "LEFT JOIN m_office of ON of.id = gp.office_id " + "JOIN m_group_level gl ON gl.id = gp.level_Id " + "LEFT JOIN m_staff sf ON sf.id = gp.staff_id " + "JOIN m_group_client gc ON gc.group_id = gp.id " + "JOIN m_client cl ON cl.id = gc.client_id " + "LEFT JOIN m_loan ln ON cl.id = ln.client_id AND ln.group_id is not null AND ( ln.loan_status_id = 300 OR ( ln.loan_status_id =200 AND ln.expected_disbursedon_date <= :dueDate )) " + "LEFT JOIN m_product_loan pl ON pl.id = ln.product_id " - + "JOIN m_currency rc on rc.`code` = ln.currency_code " + + "LEFT JOIN m_currency rc on rc.`code` = ln.currency_code " + "LEFT JOIN m_loan_repayment_schedule ls ON ls.loan_id = ln.id AND ls.completed_derived = 0 AND ls.duedate <= :dueDate " + "LEFT JOIN m_loan_charge lc ON lc.loan_id = ln.id AND lc.is_paid_derived = 0 AND ( lc.due_for_collection_as_of_date <= :dueDate OR lc.charge_time_enum = 1) "; - //+ "WHERE gp.hierarchy like :hierarchy " + "GROUP BY gp.id ,cl.id , ln.id " + "ORDER BY gp.id , cl.id , ln.id "; + + // + "WHERE gp.hierarchy like :hierarchy " + + // "GROUP BY gp.id ,cl.id , ln.id " + + // "ORDER BY gp.id , cl.id , ln.id "; } @Override @@ -239,8 +247,12 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + CurrencyData currencyData = null; + if (currencyCode != null) { + currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + } final BigDecimal disbursementAmount = rs.getBigDecimal("disbursementAmount"); final BigDecimal principalDue = rs.getBigDecimal("principalDue"); @@ -258,7 +270,7 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe @Override public JLGCollectionSheetData generateGroupCollectionSheet(final Long groupId, final JsonQuery query) { - + this.collectionSheetGenerateCommandFromApiJsonDeserializer.validateForGenerateCollectionSheet(query.json()); final AppUser currentUser = this.context.authenticatedUser(); @@ -276,18 +288,18 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe String sql = mapper.collectionSheetSchema(); sql += "WHERE gp.id = :groupId GROUP BY gp.id ,cl.id , ln.id ORDER BY gp.id , cl.id , ln.id"; - final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", dueDateStr).addValue("groupId", group.getId()) - .addValue("officeHierarchy", officeHierarchy); + final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", dueDateStr) + .addValue("groupId", group.getId()).addValue("officeHierarchy", officeHierarchy); final Collection collectionSheetFlatDatas = this.namedParameterjdbcTemplate.query(sql, namedParameters, mapper); return buildJLGCollectionSheet(dueDate, collectionSheetFlatDatas); } - + @Override public JLGCollectionSheetData generateCenterCollectionSheet(final Long centerId, final JsonQuery query) { - + this.collectionSheetGenerateCommandFromApiJsonDeserializer.validateForGenerateCollectionSheet(query.json()); final AppUser currentUser = this.context.authenticatedUser(); @@ -305,8 +317,8 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe String sql = mapper.collectionSheetSchema(); sql += "WHERE gp.parent_id = :centerId GROUP BY gp.id ,cl.id , ln.id ORDER BY gp.id , cl.id , ln.id"; - final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", dueDateStr).addValue("centerId", center.getId()) - .addValue("officeHierarchy", officeHierarchy); + final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", dueDateStr) + .addValue("centerId", center.getId()).addValue("officeHierarchy", officeHierarchy); final Collection collectionSheetFlatDatas = this.namedParameterjdbcTemplate.query(sql, namedParameters, mapper); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/CentersApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/CentersApiResource.java index 03bbec3ee..70dca5147 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/CentersApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/CentersApiResource.java @@ -6,6 +6,7 @@ package org.mifosplatform.portfolio.group.api; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -36,12 +37,16 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer; import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData; +import org.mifosplatform.portfolio.accountdetails.service.AccountDetailsReadPlatformService; +import org.mifosplatform.portfolio.calendar.data.CalendarData; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetData; import org.mifosplatform.portfolio.collectionsheet.service.CollectionSheetReadPlatformService; import org.mifosplatform.portfolio.group.data.CenterData; -import org.mifosplatform.portfolio.group.data.GroupAccountSummaryCollectionData; +import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.service.CenterReadPlatformService; -import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; import org.mifosplatform.portfolio.group.service.SearchParameters; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; @@ -56,26 +61,26 @@ public class CentersApiResource { private final PlatformSecurityContext context; private final CenterReadPlatformService centerReadPlatformService; - private final GroupReadPlatformService groupReadPlatformService; private final ToApiJsonSerializer centerApiJsonSerializer; private final ToApiJsonSerializer toApiJsonSerializer; - private final ToApiJsonSerializer groupSummaryToApiJsonSerializer; + private final ToApiJsonSerializer groupSummaryToApiJsonSerializer; private final ApiRequestParameterHelper apiRequestParameterHelper; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final CollectionSheetReadPlatformService collectionSheetReadPlatformService; private final FromJsonHelper fromJsonHelper; + private final AccountDetailsReadPlatformService accountDetailsReadPlatformService; + private final CalendarReadPlatformService calendarReadPlatformService; @Autowired public CentersApiResource(final PlatformSecurityContext context, final CenterReadPlatformService centerReadPlatformService, - final GroupReadPlatformService groupReadPlatformService, final ToApiJsonSerializer centerApiJsonSerializer, - final ToApiJsonSerializer toApiJsonSerializer, - final ToApiJsonSerializer groupSummaryToApiJsonSerializer, + final ToApiJsonSerializer centerApiJsonSerializer, final ToApiJsonSerializer toApiJsonSerializer, + final ToApiJsonSerializer groupSummaryToApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper) { + final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper, + final AccountDetailsReadPlatformService accountDetailsReadPlatformService, final CalendarReadPlatformService calendarReadPlatformService) { this.context = context; this.centerReadPlatformService = centerReadPlatformService; - this.groupReadPlatformService = groupReadPlatformService; this.centerApiJsonSerializer = centerApiJsonSerializer; this.toApiJsonSerializer = toApiJsonSerializer; this.groupSummaryToApiJsonSerializer = groupSummaryToApiJsonSerializer; @@ -83,6 +88,8 @@ public class CentersApiResource { this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; this.collectionSheetReadPlatformService = collectionSheetReadPlatformService; this.fromJsonHelper = fromJsonHelper; + this.accountDetailsReadPlatformService = accountDetailsReadPlatformService; + this.calendarReadPlatformService = calendarReadPlatformService; } @GET @@ -126,7 +133,8 @@ public class CentersApiResource { this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME); final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); - + CalendarData collectionMeetingCalendar = null; + Collection groups = null; CenterData center = this.centerReadPlatformService.retrieveOne(centerId); final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); @@ -134,12 +142,19 @@ public class CentersApiResource { final CenterData templateCenter = this.centerReadPlatformService.retrieveTemplate(center.officeId()); center = CenterData.withTemplate(templateCenter, center); } - + if (!associationParameters.isEmpty()) { if (associationParameters.contains("groupMembers")) { - center = CenterData.setGroups(center, this.centerReadPlatformService.retrieveAssociatedGroups(centerId)); + groups = this.centerReadPlatformService.retrieveAssociatedGroups(centerId); } + + if (associationParameters.contains("collectionMeetingCalendar")) { + collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(centerId, CalendarEntityType.CENTERS.getValue()); + } + + center = CenterData.withAssociations(center, groups, collectionMeetingCalendar); } + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.centerApiJsonSerializer.serialize(settings, center, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS); @@ -200,18 +215,20 @@ public class CentersApiResource { final CommandWrapper commandRequest = builder.activateCenter(centerId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); - }else if (is(commandParam, "generateCollectionSheet")) { + } else if (is(commandParam, "generateCollectionSheet")) { final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson); final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper); - final JLGCollectionSheetData collectionSheet = this.collectionSheetReadPlatformService.generateCenterCollectionSheet(centerId, query); + final JLGCollectionSheetData collectionSheet = this.collectionSheetReadPlatformService.generateCenterCollectionSheet(centerId, + query); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, collectionSheet, GroupingTypesApiConstants.COLLECTIONSHEET_DATA_PARAMETERS); - }else if (is(commandParam, "saveCollectionSheet")) { + } else if (is(commandParam, "saveCollectionSheet")) { final CommandWrapper commandRequest = builder.saveCenterCollectionSheet(centerId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); - } else { - throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "generateCollectionSheet", "createRole" }); + } else { + throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "generateCollectionSheet", + "createRole" }); } } @@ -221,18 +238,17 @@ public class CentersApiResource { } @GET - @Path("{centerId}/loans") + @Path("{centerId}/accounts") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public String retrieveGroupAccount(@PathParam("centerId") final Long centerId, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME); - final GroupAccountSummaryCollectionData groupAccount = this.groupReadPlatformService.retrieveGroupAccountDetails(centerId); + final AccountSummaryCollectionData groupAccount = this.accountDetailsReadPlatformService.retrieveGroupAccountDetails(centerId); - final Set GROUP_ACCOUNTS_DATA_PARAMETERS = new HashSet(Arrays.asList("pendingApprovalLoans", - "awaitingDisbursalLoans", "openLoans", "closedLoans", "anyLoanCount", "pendingApprovalLoanCount", - "awaitingDisbursalLoanCount", "activeLoanCount", "closedLoanCount")); + final Set GROUP_ACCOUNTS_DATA_PARAMETERS = new HashSet(Arrays.asList("loanAccounts", "savingsAccounts", + "memberLoanAccounts", "memberSavingsAccounts")); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.groupSummaryToApiJsonSerializer.serialize(settings, groupAccount, GROUP_ACCOUNTS_DATA_PARAMETERS); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java index 9151d270d..935ee0c00 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java @@ -52,6 +52,7 @@ public class GroupingTypesApiConstants { public static final String officeOptionsParamName = "officeOptions"; public static final String staffOptionsParamName = "staffOptions"; public static final String clientOptionsParamName = "clientOptions"; + public static final String collectionMeetingCalendar = "collectionMeetingCalendar"; public static final Set CENTER_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, idParamName, nameParamName, externalIdParamName, officeIdParamName, staffIdParamName, activeParamName, @@ -59,7 +60,7 @@ public class GroupingTypesApiConstants { public static final Set GROUP_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, idParamName, nameParamName, externalIdParamName, centerIdParamName, officeIdParamName, staffIdParamName, activeParamName, - activationDateParamName, clientMembersParamName)); + activationDateParamName, clientMembersParamName, collectionMeetingCalendar)); public static final Set GROUP_ROLES_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(roleParamName, clientIdParamName)); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java index 465507b64..18b8cd54a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.group.api; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.ws.rs.Consumes; @@ -38,13 +39,21 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer; import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.staff.data.StaffData; +import org.mifosplatform.organisation.staff.service.StaffReadPlatformService; +import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData; +import org.mifosplatform.portfolio.accountdetails.service.AccountDetailsReadPlatformService; +import org.mifosplatform.portfolio.calendar.data.CalendarData; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetData; import org.mifosplatform.portfolio.collectionsheet.service.CollectionSheetReadPlatformService; -import org.mifosplatform.portfolio.group.data.GroupAccountSummaryCollectionData; import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.data.GroupRoleData; +import org.mifosplatform.portfolio.group.data.GroupTransferData; import org.mifosplatform.portfolio.group.service.CenterReadPlatformService; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; import org.mifosplatform.portfolio.group.service.GroupRolesReadPlatformService; @@ -67,23 +76,29 @@ public class GroupsApiResource { private final ClientReadPlatformService clientReadPlatformService; private final ToApiJsonSerializer toApiJsonSerializer; private final ToApiJsonSerializer groupGeneralApiJsonSerializer; - private final ToApiJsonSerializer groupSummaryToApiJsonSerializer; + private final ToApiJsonSerializer groupSummaryToApiJsonSerializer; private final ApiRequestParameterHelper apiRequestParameterHelper; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final CollectionSheetReadPlatformService collectionSheetReadPlatformService; private final FromJsonHelper fromJsonHelper; private final GroupRolesReadPlatformService groupRolesReadPlatformService; + private final AccountDetailsReadPlatformService accountDetailsReadPlatformService; + private final CalendarReadPlatformService calendarReadPlatformService; + private final StaffReadPlatformService staffReadPlatformService; @Autowired public GroupsApiResource(final PlatformSecurityContext context, final GroupReadPlatformService groupReadPlatformService, final CenterReadPlatformService centerReadPlatformService, final ClientReadPlatformService clientReadPlatformService, final ToApiJsonSerializer toApiJsonSerializer, final ToApiJsonSerializer groupTopOfHierarchyApiJsonSerializer, - final ToApiJsonSerializer groupSummaryToApiJsonSerializer, + final ToApiJsonSerializer groupSummaryToApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper, - final GroupRolesReadPlatformService groupRolesReadPlatformService) { + final GroupRolesReadPlatformService groupRolesReadPlatformService, + final AccountDetailsReadPlatformService accountDetailsReadPlatformService, + final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService) { + this.context = context; this.groupReadPlatformService = groupReadPlatformService; this.centerReadPlatformService = centerReadPlatformService; @@ -96,6 +111,9 @@ public class GroupsApiResource { this.collectionSheetReadPlatformService = collectionSheetReadPlatformService; this.fromJsonHelper = fromJsonHelper; this.groupRolesReadPlatformService = groupRolesReadPlatformService; + this.accountDetailsReadPlatformService = accountDetailsReadPlatformService; + this.calendarReadPlatformService = calendarReadPlatformService; + this.staffReadPlatformService = staffReadPlatformService; } @GET @@ -158,9 +176,12 @@ public class GroupsApiResource { Collection membersOfGroup = null; Collection groupRoles = null; GroupRoleData selectedRole = null; + Collection calendars = null; + CalendarData collectionMeetingCalendar = null; + if (!associationParameters.isEmpty()) { if (associationParameters.contains("all")) { - associationParameters.addAll(Arrays.asList("clientMembers", "groupRoles")); + associationParameters.addAll(Arrays.asList("clientMembers", "groupRoles", "calendars", "collectionMeetingCalendar")); } if (associationParameters.contains("clientMembers")) { membersOfGroup = this.clientReadPlatformService.retrieveClientMembersOfGroup(groupId); @@ -170,25 +191,36 @@ public class GroupsApiResource { } if (associationParameters.contains("groupRoles")) { groupRoles = this.groupRolesReadPlatformService.retrieveGroupRoles(groupId); - if (CollectionUtils.isEmpty(membersOfGroup)) { + if (CollectionUtils.isEmpty(groupRoles)) { groupRoles = null; } } - if (roleId != null) { - selectedRole = this.groupRolesReadPlatformService.retrieveGroupRole(groupId, roleId); - if (selectedRole != null) { - group = GroupGeneralData.updateSelectedRole(group, selectedRole); + if (associationParameters.contains("calendars")) { + final List calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter("all"); + calendars = this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), calendarTypeOptions); + if (CollectionUtils.isEmpty(calendars)) { + calendars = null; } } + if (associationParameters.contains("collectionMeetingCalendar")) { + collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(groupId, CalendarEntityType.GROUPS.getValue()); + } + + group = GroupGeneralData.withAssocations(group, membersOfGroup, groupRoles, calendars, collectionMeetingCalendar); + } + + if (roleId != null) { + selectedRole = this.groupRolesReadPlatformService.retrieveGroupRole(groupId, roleId); + if (selectedRole != null) { + group = GroupGeneralData.updateSelectedRole(group, selectedRole); + } } final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); if (template) { final GroupGeneralData templateGroup = this.groupReadPlatformService.retrieveTemplate(group.officeId(), false, staffInSelectedOfficeOnly); - group = GroupGeneralData.withTemplateAndAssociations(templateGroup, group, membersOfGroup, groupRoles); - } else { - group = GroupGeneralData.withAssocations(group, membersOfGroup, groupRoles); + group = GroupGeneralData.withTemplate(templateGroup, group); } final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); @@ -222,7 +254,7 @@ public class GroupsApiResource { return this.toApiJsonSerializer.serialize(result); } - + @PUT @Path("{groupId}") @Consumes({ MediaType.APPLICATION_JSON }) @@ -287,6 +319,10 @@ public class GroupsApiResource { final CommandWrapper commandRequest = builder.unassignGroupStaff(groupId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); + } else if (is(commandParam, "assignStaff")) { + final CommandWrapper commandRequest = builder.assignGroupStaff(groupId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); } else if (is(commandParam, "assignRole")) { final CommandWrapper commandRequest = builder.assignRole(groupId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); @@ -299,6 +335,10 @@ public class GroupsApiResource { final CommandWrapper commandRequest = builder.updateRole(groupId, roleId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); + } else if (is(commandParam, "transferClients")) { + final CommandWrapper commandRequest = builder.transferClientsBetweenGroups(groupId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); } else { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "generateCollectionSheet", "saveCollectionSheet", "unassignStaff", "assignRole", "unassignRole", "updateassignRole" }); @@ -311,20 +351,61 @@ public class GroupsApiResource { } @GET - @Path("{groupId}/loans") + @Path("{groupId}/accounts") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String retrieveLoans(@PathParam("groupId") final Long groupId, @Context final UriInfo uriInfo) { + public String retrieveAccounts(@PathParam("groupId") final Long groupId, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission("GROUP"); - final GroupAccountSummaryCollectionData groupAccount = this.groupReadPlatformService.retrieveGroupAccountDetails(groupId); + final AccountSummaryCollectionData groupAccount = this.accountDetailsReadPlatformService.retrieveGroupAccountDetails(groupId); - final Set GROUP_ACCOUNTS_DATA_PARAMETERS = new HashSet(Arrays.asList("pendingApprovalLoans", - "awaitingDisbursalLoans", "openLoans", "closedLoans", "anyLoanCount", "pendingApprovalLoanCount", - "awaitingDisbursalLoanCount", "activeLoanCount", "closedLoanCount")); + final Set GROUP_ACCOUNTS_DATA_PARAMETERS = new HashSet(Arrays.asList("loanAccounts", "savingsAccounts", + "memberLoanAccounts", "memberSavingsAccounts")); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.groupSummaryToApiJsonSerializer.serialize(settings, groupAccount, GROUP_ACCOUNTS_DATA_PARAMETERS); } + + @GET + @Path("{groupId}/clientstransfertemplate") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveClientTranferTemplate(@Context final UriInfo uriInfo, @PathParam("groupId") final Long groupId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly) { + + this.context.authenticatedUser().validateHasReadPermission("GROUP"); + GroupGeneralData group = this.groupReadPlatformService.retrieveOne(groupId); + + final boolean transferActiveLoans = true; + final boolean inheritDestinationGroupLoanOfficer = true; + + Collection membersOfGroup = this.clientReadPlatformService.retrieveClientMembersOfGroup(groupId); + if (CollectionUtils.isEmpty(membersOfGroup)) { + membersOfGroup = null; + } + + final boolean loanOfficersOnly = false; + Collection staffOptions = null; + if (staffInSelectedOfficeOnly) { + staffOptions = this.staffReadPlatformService.retrieveAllStaffForDropdown(group.officeId()); + } else { + staffOptions = this.staffReadPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(group.officeId(), + loanOfficersOnly); + } + if (CollectionUtils.isEmpty(staffOptions)) { + staffOptions = null; + } + + Collection groupOptions = this.groupReadPlatformService.retrieveGroupsForLookup(group.officeId(), groupId); + GroupTransferData data = GroupTransferData.template(groupId, membersOfGroup, groupOptions, staffOptions, transferActiveLoans, + inheritDestinationGroupLoanOfficer); + + final Set GROUP_TRANSFERS_DATA_PARAMETERS = new HashSet(Arrays.asList("groupId", "clientOptions", "groupOptions", + "staffOptions", "transferActiveLoans", "inheritDestinationGroupLoanOfficer")); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, data, GROUP_TRANSFERS_DATA_PARAMETERS); + + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/CenterData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/CenterData.java index 277d8c85b..8de5f2068 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/CenterData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/CenterData.java @@ -11,6 +11,7 @@ import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.office.data.OfficeData; import org.mifosplatform.organisation.staff.data.StaffData; +import org.mifosplatform.portfolio.calendar.data.CalendarData; /** * Immutable data object representing groups. @@ -40,17 +41,19 @@ public class CenterData { private final Collection officeOptions; private final Collection staffOptions; private final Collection groupMembersOptions; + private final CalendarData collectionMeetingCalendar; public static CenterData template(final Long officeId, final LocalDate activationDate, final Collection officeOptions, final Collection staffOptions, final Collection groupMembersOptions) { + final CalendarData collectionMeetingCalendar = null; return new CenterData(null, null, null, null, activationDate, officeId, null, null, null, null, null, officeOptions, staffOptions, - groupMembersOptions); + groupMembersOptions, collectionMeetingCalendar); } public static CenterData withTemplate(final CenterData templateCenter, final CenterData center) { return new CenterData(center.id, center.name, center.externalId, center.status, center.activationDate, center.officeId, center.officeName, center.staffId, center.staffName, center.hierarchy, center.groupMembers, templateCenter.officeOptions, - templateCenter.staffOptions, templateCenter.groupMembersOptions); + templateCenter.staffOptions, templateCenter.groupMembersOptions, templateCenter.collectionMeetingCalendar); } public static CenterData instance(final Long id, final String name, final String externalId, final EnumOptionData status, @@ -61,21 +64,22 @@ public class CenterData { final Collection officeOptions = null; final Collection staffOptions = null; final Collection groupMembersOptions = null; + final CalendarData collectionMeetingCalendar = null; return new CenterData(id, name, externalId, status, activationDate, officeId, officeName, staffId, staffName, hierarchy, - groupMembers, officeOptions, staffOptions, groupMembersOptions); + groupMembers, officeOptions, staffOptions, groupMembersOptions, collectionMeetingCalendar); } - public static CenterData setGroups(final CenterData centerData, final Collection groupMembers) { + public static CenterData withAssociations(final CenterData centerData, final Collection groupMembers, final CalendarData collectionMeetingCalendar) { return new CenterData(centerData.id, centerData.name, centerData.externalId, centerData.status, centerData.activationDate, centerData.officeId, centerData.officeName, centerData.staffId, centerData.staffName, centerData.hierarchy, groupMembers, - centerData.officeOptions, centerData.staffOptions, centerData.groupMembersOptions); + centerData.officeOptions, centerData.staffOptions, centerData.groupMembersOptions, collectionMeetingCalendar); } private CenterData(final Long id, final String name, final String externalId, final EnumOptionData status, final LocalDate activationDate, final Long officeId, final String officeName, final Long staffId, final String staffName, final String hierarchy, final Collection groupMembers, final Collection officeOptions, - final Collection staffOptions, final Collection groupMembersOptions) { + final Collection staffOptions, final Collection groupMembersOptions, final CalendarData collectionMeetingCalendar) { this.id = id; this.name = name; this.externalId = externalId; @@ -98,6 +102,7 @@ public class CenterData { this.officeOptions = officeOptions; this.staffOptions = staffOptions; this.groupMembersOptions = groupMembersOptions; + this.collectionMeetingCalendar = collectionMeetingCalendar; } public Long officeId() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupAccountSummaryCollectionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupAccountSummaryCollectionData.java deleted file mode 100644 index c5f29e3e9..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupAccountSummaryCollectionData.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * 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.group.data; - -import java.util.List; - -public class GroupAccountSummaryCollectionData { - - private final int anyLoanCount; - private final int pendingApprovalLoanCount; - private final List pendingApprovalLoans; - private final int awaitingDisbursalLoanCount; - private final List awaitingDisbursalLoans; - private final int activeLoanCount; - private final List openLoans; - private final int closedLoanCount; - private final List closedLoans; - - @SuppressWarnings("unused") - private final int anyIndividualLoanCount; - private final int pendingApprovalIndividualLoanCount; - @SuppressWarnings("unused") - private final List pendingApprovalIndividualLoans; - private final int awaitingDisbursalIndividualLoanCount; - @SuppressWarnings("unused") - private final List awaitingDisbursalIndividualLoans; - private final int activeIndividualLoanCount; - @SuppressWarnings("unused") - private final List openIndividualLoans; - private final int closedIndividualLoanCount; - @SuppressWarnings("unused") - private final List closedIndividualLoans; - - public GroupAccountSummaryCollectionData(final List pendingApprovalLoans, - final List awaitingDisbursalLoans, final List openLoans, - final List closedLoans, final List pendingApprovalIndividualLoans, - final List awaitingDisbursalIndividualLoans, final List openIndividualLoans, - final List closedIndividualLoans) { - this.pendingApprovalLoans = pendingApprovalLoans; - this.pendingApprovalLoanCount = pendingApprovalLoans.size(); - this.awaitingDisbursalLoans = awaitingDisbursalLoans; - this.awaitingDisbursalLoanCount = awaitingDisbursalLoans.size(); - this.openLoans = openLoans; - this.activeLoanCount = openLoans.size(); - this.closedLoans = closedLoans; - this.closedLoanCount = closedLoans.size(); - - this.anyLoanCount = this.pendingApprovalLoanCount + this.awaitingDisbursalLoanCount + this.activeLoanCount + this.closedLoanCount; - - this.pendingApprovalIndividualLoans = pendingApprovalIndividualLoans; - this.pendingApprovalIndividualLoanCount = pendingApprovalIndividualLoans.size(); - this.awaitingDisbursalIndividualLoans = awaitingDisbursalIndividualLoans; - this.awaitingDisbursalIndividualLoanCount = awaitingDisbursalIndividualLoans.size(); - this.openIndividualLoans = openIndividualLoans; - this.activeIndividualLoanCount = openIndividualLoans.size(); - this.closedIndividualLoans = closedIndividualLoans; - this.closedIndividualLoanCount = closedIndividualLoans.size(); - - this.anyIndividualLoanCount = this.pendingApprovalIndividualLoanCount + this.awaitingDisbursalIndividualLoanCount - + this.activeIndividualLoanCount + this.closedIndividualLoanCount; - } - - public int getAnyLoanCount() { - return this.anyLoanCount; - } - - public int getPendingApprovalLoanCount() { - return this.pendingApprovalLoanCount; - } - - public List getPendingApprovalLoans() { - return this.pendingApprovalLoans; - } - - public int getAwaitingDisbursalLoanCount() { - return this.awaitingDisbursalLoanCount; - } - - public List getAwaitingDisbursalLoans() { - return this.awaitingDisbursalLoans; - } - - public int getActiveLoanCount() { - return this.activeLoanCount; - } - - public List getOpenLoans() { - return this.openLoans; - } - - public int getClosedLoanCount() { - return this.closedLoanCount; - } - - public List getClosedLoans() { - return this.closedLoans; - } -} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupAccountSummaryData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupAccountSummaryData.java deleted file mode 100644 index d02c746fd..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupAccountSummaryData.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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.group.data; - -/** - * Immutable data object for group loan accounts. - */ -public class GroupAccountSummaryData { - - private final Long id; - private final String externalId; - private final Long productId; - private final String productName; - private final Integer accountStatusId; - @SuppressWarnings("unused") - private final String accountNo; - - public GroupAccountSummaryData(final Long id, final String externalId, final Long productId, final String productName, - final Integer accountStatusId, final String accountNo) { - this.id = id; - this.externalId = externalId; - this.productId = productId; - this.productName = productName; - this.accountStatusId = accountStatusId; - this.accountNo = accountNo; - } - - public Long getId() { - return this.id; - } - - public String getExternalId() { - return this.externalId; - } - - public Long getProductId() { - return this.productId; - } - - public String getProductName() { - return this.productName; - } - - public Integer getAccountStatusId() { - return this.accountStatusId; - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java index 63d037eb8..722d84d1e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java @@ -12,6 +12,7 @@ import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.office.data.OfficeData; import org.mifosplatform.organisation.staff.data.StaffData; +import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.client.data.ClientData; /** @@ -40,6 +41,8 @@ public class GroupGeneralData { // associations private final Collection clientMembers; private final Collection groupRoles; + private final Collection calendarsData; + private final CalendarData collectionMeetingCalendar; // template private final Collection centerOptions; @@ -54,7 +57,7 @@ public class GroupGeneralData { final Collection groupRoles = null; return new GroupGeneralData(groupId, groupName, null, null, null, null, null, null, null, null, null, null, clientMembers, null, - null, null, null, groupRoles, null, null); + null, null, null, groupRoles, null, null, null, null); } public static GroupGeneralData template(final Long officeId, final Long centerId, final String centerName, final Long staffId, @@ -66,7 +69,7 @@ public class GroupGeneralData { final Collection groupRoles = null; return new GroupGeneralData(null, null, null, null, null, officeId, null, centerId, centerName, staffId, staffName, null, - clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, null); + clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, null, null, null); } public static GroupGeneralData withTemplate(final GroupGeneralData templatedGrouping, final GroupGeneralData grouping) { @@ -74,24 +77,15 @@ public class GroupGeneralData { grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName, grouping.hierarchy, grouping.clientMembers, templatedGrouping.centerOptions, templatedGrouping.officeOptions, templatedGrouping.staffOptions, templatedGrouping.clientOptions, grouping.groupRoles, templatedGrouping.availableRoles, - grouping.selectedRole); - } - - public static GroupGeneralData withTemplateAndAssociations(final GroupGeneralData templatedGrouping, final GroupGeneralData grouping, - final Collection membersOfGroup, final Collection groupRoles) { - return new GroupGeneralData(grouping.id, grouping.name, grouping.externalId, grouping.status, grouping.activationDate, - grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName, - grouping.hierarchy, membersOfGroup, templatedGrouping.centerOptions, templatedGrouping.officeOptions, - templatedGrouping.staffOptions, templatedGrouping.clientOptions, groupRoles, templatedGrouping.availableRoles, - grouping.selectedRole); + grouping.selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar); } public static GroupGeneralData withAssocations(final GroupGeneralData grouping, final Collection membersOfGroup, - final Collection groupRoles) { + final Collection groupRoles, final Collection calendarsData, final CalendarData collectionMeetingCalendar) { return new GroupGeneralData(grouping.id, grouping.name, grouping.externalId, grouping.status, grouping.activationDate, grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName, grouping.hierarchy, membersOfGroup, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions, - grouping.clientOptions, groupRoles, grouping.availableRoles, grouping.selectedRole); + grouping.clientOptions, groupRoles, grouping.availableRoles, grouping.selectedRole, calendarsData, collectionMeetingCalendar); } public static GroupGeneralData instance(final Long id, final String name, final String externalId, final EnumOptionData status, @@ -106,10 +100,12 @@ public class GroupGeneralData { final Collection groupRoles = null; final Collection availableRoles = null; final GroupRoleData role = null; + final Collection calendarsData = null; + final CalendarData collectionMeetingCalendar = null; return new GroupGeneralData(id, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId, staffName, hierarchy, clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, - role); + role, calendarsData, collectionMeetingCalendar); } private GroupGeneralData(final Long id, final String name, final String externalId, final EnumOptionData status, @@ -117,7 +113,8 @@ public class GroupGeneralData { final Long staffId, final String staffName, final String hierarchy, final Collection clientMembers, final Collection centerOptions, final Collection officeOptions, final Collection staffOptions, final Collection clientOptions, - final Collection groupRoles, final Collection availableRoles, final GroupRoleData role) { + final Collection groupRoles, final Collection availableRoles, final GroupRoleData role, + final Collection calendarsData, final CalendarData collectionMeetingCalendar) { this.id = id; this.name = name; this.externalId = externalId; @@ -152,6 +149,8 @@ public class GroupGeneralData { this.groupRoles = groupRoles; this.availableRoles = availableRoles; this.selectedRole = role; + this.calendarsData = calendarsData; + this.collectionMeetingCalendar = collectionMeetingCalendar; } public Long getId() { @@ -174,7 +173,7 @@ public class GroupGeneralData { return new GroupGeneralData(grouping.id, grouping.name, grouping.externalId, grouping.status, grouping.activationDate, grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName, grouping.hierarchy, grouping.clientMembers, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions, - grouping.clientOptions, grouping.groupRoles, grouping.availableRoles, selectedRole); + grouping.clientOptions, grouping.groupRoles, grouping.availableRoles, selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupTransferData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupTransferData.java new file mode 100644 index 000000000..ee1f4e10b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupTransferData.java @@ -0,0 +1,40 @@ +package org.mifosplatform.portfolio.group.data; + +import java.util.Collection; + +import org.mifosplatform.organisation.staff.data.StaffData; +import org.mifosplatform.portfolio.client.data.ClientData; + +public class GroupTransferData { + + @SuppressWarnings("unused") + private final Long groupId; + @SuppressWarnings("unused") + private final Collection clientOptions; + @SuppressWarnings("unused") + private final Collection groupOptions; + @SuppressWarnings("unused") + private final Collection staffOptions; + @SuppressWarnings("unused") + private final boolean transferActiveLoans; + @SuppressWarnings("unused") + private final boolean inheritDestinationGroupLoanOfficer; + + private GroupTransferData(final Long groupId, final Collection clientOptions, + final Collection groupOptions, final Collection staffOptions, final boolean transferActiveLoans, + final boolean inheritDestinationGroupLoanOfficer) { + this.groupId = groupId; + this.clientOptions = clientOptions; + this.groupOptions = groupOptions; + this.staffOptions = staffOptions; + this.transferActiveLoans = transferActiveLoans; + this.inheritDestinationGroupLoanOfficer = inheritDestinationGroupLoanOfficer; + } + + public static GroupTransferData template(final Long groupId, final Collection clientOptions, + final Collection groupOptions, final Collection staffOptions, final boolean transferActiveLoans, + final boolean inheritDestinationGroupLoanOfficer) { + return new GroupTransferData(groupId, clientOptions, groupOptions, staffOptions, transferActiveLoans, + inheritDestinationGroupLoanOfficer); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java index 4f128779b..65a9915de 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java @@ -384,4 +384,13 @@ public final class Group extends AbstractPersistable { public boolean isCenter() { return this.groupLevel.isCenter(); } + + public boolean isChildClient(final Long clientId){ + if (clientId != null && this.clientMembers != null && !this.clientMembers.isEmpty()) { + for (Client client : this.clientMembers) { + if(client.getId().equals(clientId)) return true; + } + } + return false; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRepository.java index f99bdabbf..4a0402fef 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRepository.java @@ -5,9 +5,11 @@ */ package org.mifosplatform.portfolio.group.domain; +import java.util.Collection; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface GroupRepository extends JpaRepository, JpaSpecificationExecutor { - + Collection findByParentId(Long parentId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRole.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRole.java index 07a68e612..045d52570 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRole.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRole.java @@ -19,7 +19,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_group_roles") public class GroupRole extends AbstractPersistable { - @SuppressWarnings("unused") @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "group_id") private Group group; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/ClientNotInGroupException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/ClientNotInGroupException.java index 951ec72eb..c87e5eb42 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/ClientNotInGroupException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/ClientNotInGroupException.java @@ -10,8 +10,12 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRul public class ClientNotInGroupException extends AbstractPlatformDomainRuleException { public ClientNotInGroupException(final Long clientId, final Long groupId) { - super("error.msg.group.client.not.in.group", "Client with identifier " + clientId + " is not in Group with identifier " + groupId, - clientId, groupId); + this("group.client.not.in.group", "Client with identifier " + clientId + " is not in Group with identifier " + groupId, clientId, groupId); + + } + + public ClientNotInGroupException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) { + super("error.msg." + postFix , defaultUserMessage, defaultUserMessageArgs); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/handler/AssignGroupStaffCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/handler/AssignGroupStaffCommandHandler.java new file mode 100644 index 000000000..1896e641b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/handler/AssignGroupStaffCommandHandler.java @@ -0,0 +1,31 @@ +/** + * 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.group.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.group.service.GroupingTypesWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class AssignGroupStaffCommandHandler implements NewCommandSourceHandler { + + private final GroupingTypesWritePlatformService groupWritePlatformService; + + @Autowired + public AssignGroupStaffCommandHandler(final GroupingTypesWritePlatformService groupWritePlatformService) { + this.groupWritePlatformService = groupWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.groupWritePlatformService.assignGroupOrCenterStaff(command.entityId(), command); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/serialization/GroupingTypesDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/serialization/GroupingTypesDataValidator.java index bd0b242c5..60813c17a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/serialization/GroupingTypesDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/serialization/GroupingTypesDataValidator.java @@ -345,6 +345,28 @@ public final class GroupingTypesDataValidator { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + public void validateForAssignStaff(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + + final Set supportedParametersAssignStaff = new HashSet(Arrays.asList(GroupingTypesApiConstants.staffIdParamName)); + + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersAssignStaff); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME); + + final String staffIdParameterName = GroupingTypesApiConstants.staffIdParamName; + final Long staffId = this.fromApiJsonHelper.extractLongNamed(staffIdParameterName, element); + baseDataValidator.reset().parameter(staffIdParameterName).value(staffId).notNull().longGreaterThanZero(); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + public void validateForAssociateClients(final String json){ if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformService.java index 9a38c6aed..f83e9d84e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformService.java @@ -8,8 +8,6 @@ package org.mifosplatform.portfolio.group.service; import java.util.Collection; import org.mifosplatform.infrastructure.core.service.Page; -import org.mifosplatform.portfolio.group.data.GroupAccountSummaryCollectionData; -import org.mifosplatform.portfolio.group.data.GroupAccountSummaryData; import org.mifosplatform.portfolio.group.data.GroupGeneralData; public interface GroupReadPlatformService { @@ -19,9 +17,6 @@ public interface GroupReadPlatformService { Page retrieveAll(SearchParameters searchParameters); GroupGeneralData retrieveOne(Long groupId); - - // - GroupAccountSummaryCollectionData retrieveGroupAccountDetails(Long groupId); - - Collection retrieveGroupLoanAccountsByLoanOfficerId(Long groupId, Long loanOfficerId); + + Collection retrieveGroupsForLookup(Long officeId, Long groupId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformServiceImpl.java index d75be5e33..931e052e3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformServiceImpl.java @@ -7,9 +7,7 @@ package org.mifosplatform.portfolio.group.service; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; import org.apache.commons.lang.StringUtils; import org.mifosplatform.infrastructure.codes.data.CodeValueData; @@ -26,11 +24,8 @@ import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.organisation.staff.service.StaffReadPlatformService; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; -import org.mifosplatform.portfolio.client.service.LoanStatusMapper; import org.mifosplatform.portfolio.group.api.GroupingTypesApiConstants; import org.mifosplatform.portfolio.group.data.CenterData; -import org.mifosplatform.portfolio.group.data.GroupAccountSummaryCollectionData; -import org.mifosplatform.portfolio.group.data.GroupAccountSummaryData; import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.domain.GroupTypes; import org.mifosplatform.portfolio.group.exception.GroupNotFoundException; @@ -211,112 +206,24 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService { } @Override - public GroupAccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId) { - try { - this.context.authenticatedUser(); - - // Check if group exists - // retrieveGroup(groupId); - - final List pendingApprovalLoans = new ArrayList(); - final List awaitingDisbursalLoans = new ArrayList(); - final List openLoans = new ArrayList(); - final List closedLoans = new ArrayList(); - - final GroupLoanAccountSummaryDataMapper rm = new GroupLoanAccountSummaryDataMapper(); - - String sql = "select " + rm.loanAccountSummarySchema() + " where l.group_id = ? and l.client_id is null"; - - List results = this.jdbcTemplate.query(sql, rm, new Object[] { groupId }); - if (results != null) { - for (final GroupAccountSummaryData row : results) { - - final LoanStatusMapper statusMapper = new LoanStatusMapper(row.getAccountStatusId()); - - if (statusMapper.isOpen()) { - openLoans.add(row); - } else if (statusMapper.isAwaitingDisbursal()) { - awaitingDisbursalLoans.add(row); - } else if (statusMapper.isPendingApproval()) { - pendingApprovalLoans.add(row); - } else { - closedLoans.add(row); - } - } - } - - final List pendingApprovalIndividualLoans = new ArrayList(); - final List awaitingDisbursalIndividualLoans = new ArrayList(); - final List openIndividualLoans = new ArrayList(); - final List closedIndividualLoans = new ArrayList(); - - sql = "select " + rm.loanAccountSummarySchema() + " where l.group_id = ? and l.client_id is not null"; - - results = this.jdbcTemplate.query(sql, rm, new Object[] { groupId }); - if (results != null) { - for (final GroupAccountSummaryData row : results) { - - final LoanStatusMapper statusMapper = new LoanStatusMapper(row.getAccountStatusId()); - - if (statusMapper.isOpen()) { - openIndividualLoans.add(row); - } else if (statusMapper.isAwaitingDisbursal()) { - awaitingDisbursalIndividualLoans.add(row); - } else if (statusMapper.isPendingApproval()) { - pendingApprovalIndividualLoans.add(row); - } else { - closedIndividualLoans.add(row); - } - } - } - - return new GroupAccountSummaryCollectionData(pendingApprovalLoans, awaitingDisbursalLoans, openLoans, closedLoans, - pendingApprovalIndividualLoans, awaitingDisbursalIndividualLoans, openIndividualLoans, closedIndividualLoans); - } catch (final EmptyResultDataAccessException e) { - throw new GroupNotFoundException(groupId); - } - } - - @Override - public Collection retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId, final Long loanOfficerId) { - + public Collection retrieveGroupsForLookup(final Long officeId, final Long groupId) { this.context.authenticatedUser(); - - // Check if group exists - // retrieveGroup(groupId); - - final GroupLoanAccountSummaryDataMapper rm = new GroupLoanAccountSummaryDataMapper(); - - final String sql = "select " + rm.loanAccountSummarySchema() - + " where l.group_id = ? and l.client_id is null and l.loan_officer_id = ?"; - - final List loanAccounts = this.jdbcTemplate.query(sql, rm, new Object[] { groupId, loanOfficerId }); - - return loanAccounts; + GroupLookupDataMapper rm = new GroupLookupDataMapper(); + String sql = "Select " + rm.schema() + " where g.office_id=? and g.id !=?"; + return this.jdbcTemplate.query(sql, rm, new Object[] { officeId, groupId }); } - private static final class GroupLoanAccountSummaryDataMapper implements RowMapper { + private static final class GroupLookupDataMapper implements RowMapper { - public String loanAccountSummarySchema() { - - final StringBuilder accountsSummary = new StringBuilder("l.id as id, l.external_id as externalId,"); - accountsSummary.append("l.product_id as productId, lp.name as productName,").append("l.loan_status_id as statusId, ") - .append("l.account_no as accountNo ").append("from m_loan l ") - .append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id "); - - return accountsSummary.toString(); + public final String schema() { + return "g.id as id, g.display_name as displayName from m_group g"; } @Override - public GroupAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - + public GroupGeneralData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { final Long id = JdbcSupport.getLong(rs, "id"); - final String externalId = rs.getString("externalId"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - final String loanProductName = rs.getString("productName"); - final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId"); - final String accountNo = rs.getString("accountNo"); - return new GroupAccountSummaryData(id, externalId, productId, loanProductName, loanStatusId, accountNo); + final String displayName = rs.getString("displayName"); + return GroupGeneralData.lookup(id, displayName); } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformService.java index 351f4a3d7..928379deb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformService.java @@ -24,6 +24,8 @@ public interface GroupingTypesWritePlatformService { CommandProcessingResult unassignGroupOrCenterStaff(Long groupId, JsonCommand command); + CommandProcessingResult assignGroupOrCenterStaff(Long groupId, JsonCommand command); + CommandProcessingResult associateClientsToGroup(Long groupId, JsonCommand command); CommandProcessingResult disassociateClientsFromGroup(Long groupId, JsonCommand command); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java index b9b8214e2..d65b348bd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java @@ -354,7 +354,35 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group .build(); } + + @Override + public CommandProcessingResult assignGroupOrCenterStaff(final Long grouptId, final JsonCommand command) { + this.context.authenticatedUser(); + + final Map actualChanges = new LinkedHashMap(5); + + this.fromApiJsonDeserializer.validateForAssignStaff(command.json()); + + final Group groupForUpdate = this.groupRepository.findOneWithNotFoundDetection(grouptId); + + Staff staff = null; + final Long staffId = command.longValueOfParameterNamed(GroupingTypesApiConstants.staffIdParamName); + staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, groupForUpdate.getOffice().getHierarchy()); + groupForUpdate.updateStaff(staff); + + this.groupRepository.saveAndFlush(groupForUpdate); + + actualChanges.put(GroupingTypesApiConstants.staffIdParamName, staffId); + + return new CommandProcessingResultBuilder() // + .withOfficeId(groupForUpdate.officeId()) // + .withEntityId(groupForUpdate.getId()) // + .withGroupId(grouptId) // + .with(actualChanges) // + .build(); + } + @Transactional @Override public CommandProcessingResult deleteGroup(final Long groupId) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java index 0c59ccc06..5a487002d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -45,7 +45,7 @@ import org.springframework.stereotype.Component; @Scope("singleton") public class LoanTransactionsApiResource { - private final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "type", "date", "currency", "amount")); + private final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "type", "date", "currency", "amount", "externalId")); private final String resourceNameForPermissions = "LOAN"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java index a79bb13d9..f7d69538c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java @@ -32,6 +32,7 @@ public class LoanTransactionData { private final BigDecimal interestPortion; private final BigDecimal feeChargesPortion; private final BigDecimal penaltyChargesPortion; + private final String externalId; // templates final Collection paymentTypeOptions; @@ -40,20 +41,20 @@ public class LoanTransactionData { return new LoanTransactionData(loanTransactionData.id, loanTransactionData.type, loanTransactionData.paymentDetailData, loanTransactionData.currency, loanTransactionData.date, loanTransactionData.amount, loanTransactionData.principalPortion, loanTransactionData.interestPortion, loanTransactionData.feeChargesPortion, loanTransactionData.penaltyChargesPortion, - paymentTypeOptions); + paymentTypeOptions, loanTransactionData.externalId); } public LoanTransactionData(final Long id, final LoanTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount, final BigDecimal principalPortion, - final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion) { + final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final String externalId) { this(id, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion, feeChargesPortion, - penaltyChargesPortion, null); + penaltyChargesPortion, null, externalId); } public LoanTransactionData(final Long id, final LoanTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, - final Collection paymentTypeOptions) { + final Collection paymentTypeOptions, final String externalId) { this.id = id; this.type = transactionType; this.paymentDetailData = paymentDetailData; @@ -65,6 +66,7 @@ public class LoanTransactionData { this.feeChargesPortion = feeChargesPortion; this.penaltyChargesPortion = penaltyChargesPortion; this.paymentTypeOptions = paymentTypeOptions; + this.externalId = externalId; } public LocalDate dateOf() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index d7ccd778d..b88a003ce 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -55,7 +55,8 @@ import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil; -import org.mifosplatform.portfolio.calendar.service.CalendarHelper; +import org.mifosplatform.portfolio.accountdetails.domain.AccountType; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.collateral.data.CollateralData; @@ -148,27 +149,22 @@ public class Loan extends AbstractPersistable { @Column(name = "submittedon_date") private Date submittedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "submittedon_userid", nullable = true) private AppUser submittedBy; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "rejectedon_date") private Date rejectedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "rejectedon_userid", nullable = true) private AppUser rejectedBy; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "withdrawnon_date") private Date withdrawnOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "withdrawnon_userid", nullable = true) private AppUser withdrawnBy; @@ -177,7 +173,6 @@ public class Loan extends AbstractPersistable { @Column(name = "approvedon_date") private Date approvedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "approvedon_userid", nullable = true) private AppUser approvedBy; @@ -190,7 +185,6 @@ public class Loan extends AbstractPersistable { @Column(name = "disbursedon_date") private Date actualDisbursementDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "disbursedon_userid", nullable = true) private AppUser disbursedBy; @@ -199,12 +193,10 @@ public class Loan extends AbstractPersistable { @Column(name = "closedon_date") private Date closedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "closedon_userid", nullable = true) private AppUser closedBy; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "writtenoffon_date") private Date writtenOffOnDate; @@ -213,12 +205,10 @@ public class Loan extends AbstractPersistable { @Column(name = "rescheduledon_date") private Date rescheduledOnDate; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "expected_maturedon_date") private Date expectedMaturityDate; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "maturedon_date") private Date actualMaturityDate; @@ -231,7 +221,6 @@ public class Loan extends AbstractPersistable { @Column(name = "interest_calculated_from_date") private Date interestChargedFromDate; - @SuppressWarnings("unused") @Column(name = "total_overpaid_derived", scale = 6, precision = 19) private BigDecimal totalOverpaid; @@ -381,7 +370,7 @@ public class Loan extends AbstractPersistable { Money chargesDue = Money.of(getCurrency(), BigDecimal.ZERO); - for (LoanCharge charge : setOfLoanCharges()) { + for (final LoanCharge charge : setOfLoanCharges()) { if (charge.isDueAtDisbursement()) { chargesDue = chargesDue.plus(charge.amount()); } @@ -391,14 +380,14 @@ public class Loan extends AbstractPersistable { } private Set associateChargesWithThisLoan(final Set loanCharges) { - for (LoanCharge loanCharge : loanCharges) { + for (final LoanCharge loanCharge : loanCharges) { loanCharge.update(this); } return loanCharges; } private Set associateWithThisLoan(final Set collateral) { - for (LoanCollateral item : collateral) { + for (final LoanCollateral item : collateral) { item.associateWith(this); } return collateral; @@ -454,13 +443,13 @@ public class Loan extends AbstractPersistable { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); - for (LoanTransaction transaction : changedTransactionDetail.getNewTransactions()) { + for (final LoanTransaction transaction : changedTransactionDetail.getNewTransactions()) { transaction.updateLoan(this); } this.loanTransactions.addAll(changedTransactionDetail.getNewTransactions()); } else { // just reprocess the loan schedule only for now. - LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); + final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges()); } @@ -496,7 +485,7 @@ public class Loan extends AbstractPersistable { transactionDate = suppliedTransactionDate; } else { transactionDate = loanCharge.getDueLocalDate(); - LocalDate currentDate = DateUtils.getLocalDateOfTenant(); + final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); // if loan charge is to be applied on a future date, the loan // transaction would show todays date as applied date @@ -548,7 +537,7 @@ public class Loan extends AbstractPersistable { // if you want ability to remove loan charges that are waived. validateLoanChargeIsNotWaived(loanCharge); - boolean removed = setOfLoanCharges().remove(loanCharge); + final boolean removed = setOfLoanCharges().remove(loanCharge); if (removed) { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } @@ -577,7 +566,7 @@ public class Loan extends AbstractPersistable { private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final LoanCharge loanCharge) { if (loanCharge.isDueAtDisbursement()) { LoanTransaction transactionToRemove = null; - for (LoanTransaction transaction : this.loanTransactions) { + for (final LoanTransaction transaction : this.loanTransactions) { if (transaction.isRepaymentAtDisbursement()) { final MonetaryCurrency currency = loanCurrency(); @@ -629,7 +618,7 @@ public class Loan extends AbstractPersistable { getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); } else { // reprocess loan schedule based on charge been waived. - LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); + final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges()); } @@ -686,7 +675,7 @@ public class Loan extends AbstractPersistable { getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); } else { // reprocess loan schedule based on charge been waived. - LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); + final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges()); } @@ -731,12 +720,12 @@ public class Loan extends AbstractPersistable { } public void updateLoanOfficerOnLoanApplication(final Staff newLoanOfficer) { - if (!this.isSubmittedAndPendingApproval()) { + if (!isSubmittedAndPendingApproval()) { Long loanOfficerId = null; if (this.loanOfficer != null) { loanOfficerId = this.loanOfficer.getId(); } - throw new LoanOfficerAssignmentException(this.getId(), loanOfficerId); + throw new LoanOfficerAssignmentException(getId(), loanOfficerId); } this.loanOfficer = newLoanOfficer; } @@ -762,15 +751,15 @@ public class Loan extends AbstractPersistable { public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) { this.repaymentScheduleInstallments.clear(); - for (LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { + for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { if (scheduledLoanInstallment.isRepaymentPeriod()) { - LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this, + final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this, scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue()); - this.addRepaymentScheduleInstallment(installment); + addRepaymentScheduleInstallment(installment); } } @@ -811,7 +800,7 @@ public class Loan extends AbstractPersistable { final Map actualChanges = this.loanRepaymentScheduleDetail.updateLoanApplicationAttributes(command, aprCalculator); if (!actualChanges.isEmpty()) { - boolean recalculateLoanSchedule = !(actualChanges.size() == 1 && actualChanges.containsKey("inArrearsTolerance")); + final boolean recalculateLoanSchedule = !(actualChanges.size() == 1 && actualChanges.containsKey("inArrearsTolerance")); actualChanges.put("recalculateLoanSchedule", recalculateLoanSchedule); } @@ -961,14 +950,14 @@ public class Loan extends AbstractPersistable { throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, getSubmittedOnDate()); } - if (!(client == null)) { - if (getSubmittedOnDate().isBefore(client.getActivationLocalDate())) { + if (!(this.client == null)) { + if (getSubmittedOnDate().isBefore(this.client.getActivationLocalDate())) { final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, getSubmittedOnDate()); } - } else if (!(group == null)) { - if (getSubmittedOnDate().isBefore(group.getActivationLocalDate())) { + } else if (!(this.group == null)) { + if (getSubmittedOnDate().isBefore(this.group.getActivationLocalDate())) { final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, getSubmittedOnDate()); @@ -993,7 +982,7 @@ public class Loan extends AbstractPersistable { actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges)); actualChanges.put("recalculateLoanSchedule", true); - for (LoanCharge loanCharge : possiblyModifedLoanCharges) { + for (final LoanCharge loanCharge : possiblyModifedLoanCharges) { validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); } } @@ -1037,10 +1026,10 @@ public class Loan extends AbstractPersistable { CollateralData[] existingLoanCollateral = null; - List loanCollateralList = new ArrayList(); - for (LoanCollateral loanCollateral : setOfLoanCollateral) { + final List loanCollateralList = new ArrayList(); + for (final LoanCollateral loanCollateral : setOfLoanCollateral) { - CollateralData data = loanCollateral.toData(); + final CollateralData data = loanCollateral.toData(); loanCollateralList.add(data); } @@ -1054,8 +1043,8 @@ public class Loan extends AbstractPersistable { LoanChargeCommand[] existingLoanCharges = null; - List loanChargesList = new ArrayList(); - for (LoanCharge loanCharge : setOfLoanCharges) { + final List loanChargesList = new ArrayList(); + for (final LoanCharge loanCharge : setOfLoanCharges) { loanChargesList.add(loanCharge.toCommand()); } @@ -1065,9 +1054,9 @@ public class Loan extends AbstractPersistable { } private void removeFirstDisbursementTransaction() { - for (LoanTransaction loanTransaction : loanTransactions) { + for (final LoanTransaction loanTransaction : this.loanTransactions) { if (loanTransaction.isDisbursement()) { - loanTransactions.remove(loanTransaction); + this.loanTransactions.remove(loanTransaction); break; } } @@ -1085,7 +1074,7 @@ public class Loan extends AbstractPersistable { from = LoanStatus.fromInt(this.loanStatus); } - LoanStatus statusEnum = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, from); + final LoanStatus statusEnum = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, from); this.loanStatus = statusEnum.getValue(); this.externalId = externalId; @@ -1104,10 +1093,11 @@ public class Loan extends AbstractPersistable { throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn); } - if (client != null && client.isActivatedAfter(submittedOn)) { + if (this.client != null && this.client.isActivatedAfter(submittedOn)) { final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, submittedOn); - } else if (group != null && group.isActivatedAfter(submittedOn)) { + } + if (this.group != null && this.group.isActivatedAfter(submittedOn)) { final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, submittedOn); } @@ -1120,17 +1110,17 @@ public class Loan extends AbstractPersistable { } // charges are optional - for (LoanCharge loanCharge : setOfLoanCharges()) { + for (final LoanCharge loanCharge : setOfLoanCharges()) { validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); } - - //validate if disbursement date is a holiday or a non-working day + + // validate if disbursement date is a holiday or a non-working day validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay); validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); } - + private LocalDate determineExpectedMaturityDate() { - int numberOfInstallments = this.repaymentScheduleInstallments.size(); + final int numberOfInstallments = this.repaymentScheduleInstallments.size(); return this.repaymentScheduleInstallments.get(numberOfInstallments - 1).getDueDate(); } @@ -1144,7 +1134,7 @@ public class Loan extends AbstractPersistable { this.loanStatus = statusEnum.getValue(); actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); - LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate"); + final LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate"); final Locale locale = new Locale(command.locale()); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); @@ -1292,9 +1282,9 @@ public class Loan extends AbstractPersistable { private Collection findExistingTransactionIds() { - Collection ids = new ArrayList(); + final Collection ids = new ArrayList(); - for (LoanTransaction transaction : this.loanTransactions) { + for (final LoanTransaction transaction : this.loanTransactions) { ids.add(transaction.getId()); } @@ -1305,7 +1295,7 @@ public class Loan extends AbstractPersistable { final Collection ids = new ArrayList(); - for (LoanTransaction transaction : this.loanTransactions) { + for (final LoanTransaction transaction : this.loanTransactions) { if (transaction.isReversed()) { ids.add(transaction.getId()); } @@ -1322,7 +1312,7 @@ public class Loan extends AbstractPersistable { updateLoanToPreDisbursalState(); - final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); + final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { existingTransactionIds.addAll(findExistingTransactionIds()); @@ -1332,6 +1322,7 @@ public class Loan extends AbstractPersistable { actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); + final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); this.actualDisbursementDate = actualDisbursementDate.toDate(); this.disbursedBy = currentUser; @@ -1341,21 +1332,22 @@ public class Loan extends AbstractPersistable { actualChanges.put("dateFormat", command.dateFormat()); actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate")); - //validate if disbursement date is a holiday or a non-working day + // validate if disbursement date is a holiday or a non-working day validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay); validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); - - handleDisbursementTransaction(paymentDetail, actualDisbursementDate); + + handleDisbursementTransaction(paymentDetail, actualDisbursementDate, txnExternalId); if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) { - regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, + holidays, workingDays); updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); } else { updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); updateLoanSummaryDerivedFields(); } - Money interestApplied = Money.of(this.getCurrency(), this.summary.getTotalInterestCharged()); + final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); final LoanTransaction interestAppliedTransaction = LoanTransaction.applyInterest(this, interestApplied, actualDisbursementDate); this.loanTransactions.add(interestAppliedTransaction); } @@ -1365,8 +1357,8 @@ public class Loan extends AbstractPersistable { private void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) { - for (LoanRepaymentScheduleInstallment repaymentPeriod : this.repaymentScheduleInstallments) { - repaymentPeriod.updateDerivedFields(this.loanCurrency(), actualDisbursementDate); + for (final LoanRepaymentScheduleInstallment repaymentPeriod : this.repaymentScheduleInstallments) { + repaymentPeriod.updateDerivedFields(loanCurrency(), actualDisbursementDate); } } @@ -1375,8 +1367,8 @@ public class Loan extends AbstractPersistable { * details/state. */ private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory, - final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays) { + final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, + final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); @@ -1386,19 +1378,20 @@ public class Loan extends AbstractPersistable { final Integer loanTermFrequency = this.termFrequency; final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); - LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail); - LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges, isHolidayEnabled, holidays, workingDays); + final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges, isHolidayEnabled, holidays, workingDays); updateLoanSchedule(loanSchedule); } - private LoanTransaction handleDisbursementTransaction(final PaymentDetail paymentDetail, final LocalDate disbursedOn) { + private LoanTransaction handleDisbursementTransaction(final PaymentDetail paymentDetail, final LocalDate disbursedOn, + final String txnExternalId) { // track disbursement transaction final LoanTransaction disbursementTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(), - paymentDetail, disbursedOn); + paymentDetail, disbursedOn, txnExternalId); disbursementTransaction.updateLoan(this); this.loanTransactions.add(disbursementTransaction); @@ -1412,8 +1405,8 @@ public class Loan extends AbstractPersistable { final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency()); if (totalFeeChargesDueAtDisbursement.isGreaterThanZero()) { - LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(totalFeeChargesDueAtDisbursement, null, disbursedOn); - Money zero = Money.zero(getCurrency()); + final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(totalFeeChargesDueAtDisbursement, null, disbursedOn, txnExternalId); + final Money zero = Money.zero(getCurrency()); chargesPayment.updateComponents(zero, zero, totalFeeChargesDueAtDisbursement, zero); chargesPayment.updateLoan(this); this.loanTransactions.add(chargesPayment); @@ -1423,11 +1416,11 @@ public class Loan extends AbstractPersistable { * "APPLY Charge" transactions are created for all other fees ( * which are created during disbursal but not repaid) **/ - for (LoanCharge charge : setOfLoanCharges()) { + for (final LoanCharge charge : setOfLoanCharges()) { if (charge.isDueAtDisbursement()) { charge.markAsFullyPaid(); // Add "Loan Charge Paid By" details to this transaction - LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount()); + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount()); chargesPayment.getLoanChargesPaid().add(loanChargePaidBy); } else { handleChargeAppliedTransaction(charge, disbursedOn); @@ -1478,7 +1471,7 @@ public class Loan extends AbstractPersistable { private final void reverseExistingTransactions() { - for (LoanTransaction transaction : this.loanTransactions) { + for (final LoanTransaction transaction : this.loanTransactions) { transaction.reverse(); } } @@ -1486,15 +1479,15 @@ public class Loan extends AbstractPersistable { private void updateLoanToPreDisbursalState() { this.actualDisbursementDate = null; - for (LoanCharge charge : setOfLoanCharges()) { + for (final LoanCharge charge : setOfLoanCharges()) { charge.resetToOriginal(loanCurrency()); } - for (LoanRepaymentScheduleInstallment currentInstallment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment currentInstallment : this.repaymentScheduleInstallments) { currentInstallment.resetDerivedComponents(); } - LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); + final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges()); updateLoanSummaryDerivedFields(); @@ -1507,7 +1500,7 @@ public class Loan extends AbstractPersistable { existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(waiveInterestTransaction, + final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(waiveInterestTransaction, loanLifecycleStateMachine, null); return changedTransactionDetail; @@ -1520,11 +1513,11 @@ public class Loan extends AbstractPersistable { validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays); validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); - + existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(repaymentTransaction, + final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(repaymentTransaction, loanLifecycleStateMachine, null); return changedTransactionDetail; @@ -1541,7 +1534,7 @@ public class Loan extends AbstractPersistable { loanTransaction.updateLoan(this); - boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction, this.loanTransactions); + final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction, this.loanTransactions); if (loanTransaction.isNotZero(loanCurrency())) { this.loanTransactions.add(loanTransaction); @@ -1552,12 +1545,12 @@ public class Loan extends AbstractPersistable { throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.repayment.or.waiver.transaction", errorMessage); } - LocalDate loanTransactionDate = loanTransaction.getTransactionDate(); - if (loanTransactionDate.isBefore(this.getDisbursementDate())) { + final LocalDate loanTransactionDate = loanTransaction.getTransactionDate(); + if (loanTransactionDate.isBefore(getDisbursementDate())) { final String errorMessage = "The transaction date cannot be before the loan disbursement date: " + getApprovedOnDate().toString(); throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage, - loanTransactionDate, this.getDisbursementDate()); + loanTransactionDate, getDisbursementDate()); } if (loanTransactionDate.isAfter(DateUtils.getLocalDateOfTenant())) { @@ -1586,7 +1579,7 @@ public class Loan extends AbstractPersistable { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); - for (LoanTransaction newLoanTransaction : changedTransactionDetail.getNewTransactions()) { + for (final LoanTransaction newLoanTransaction : changedTransactionDetail.getNewTransactions()) { newLoanTransaction.updateLoan(this); } this.loanTransactions.addAll(changedTransactionDetail.getNewTransactions()); @@ -1600,20 +1593,20 @@ public class Loan extends AbstractPersistable { } private List retreiveListOfTransactionsPostDisbursement() { - List repaymentsOrWaivers = new ArrayList(); - for (LoanTransaction transaction : this.loanTransactions) { + final List repaymentsOrWaivers = new ArrayList(); + for (final LoanTransaction transaction : this.loanTransactions) { if (!transaction.isDisbursement() && transaction.isNotReversed()) { repaymentsOrWaivers.add(transaction); } } - LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); + final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); Collections.sort(repaymentsOrWaivers, transactionComparator); return repaymentsOrWaivers; } private void doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - if (this.isOverPaid()) { + if (isOverPaid()) { // FIXME - kw - update account balance to negative amount. handleLoanOverpayment(loanLifecycleStateMachine); } else if (this.summary.isRepaidInFull(loanCurrency())) { @@ -1623,7 +1616,7 @@ public class Loan extends AbstractPersistable { private void handleLoanRepaymentInFull(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, LoanStatus.fromInt(this.loanStatus)); + final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, LoanStatus.fromInt(this.loanStatus)); this.loanStatus = statusEnum.getValue(); this.closedOnDate = transactionDate.toDate(); @@ -1632,7 +1625,7 @@ public class Loan extends AbstractPersistable { private void handleLoanOverpayment(final LoanLifecycleStateMachine loanLifecycleStateMachine) { - LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, LoanStatus.fromInt(this.loanStatus)); + final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, LoanStatus.fromInt(this.loanStatus)); this.loanStatus = statusEnum.getValue(); this.closedOnDate = null; @@ -1644,8 +1637,8 @@ public class Loan extends AbstractPersistable { boolean isChronologicallyLatestRepaymentOrWaiver = true; - LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); - for (LoanTransaction previousTransaction : loanTransactions) { + final LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); + for (final LoanTransaction previousTransaction : loanTransactions) { if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed()) { if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate()) || currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) { @@ -1662,8 +1655,8 @@ public class Loan extends AbstractPersistable { boolean isChronologicallyLatestRepaymentOrWaiver = true; - LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); - for (LoanTransaction previousTransaction : loanTransactions) { + final LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); + for (final LoanTransaction previousTransaction : loanTransactions) { if (previousTransaction.isNotReversed()) { if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate()) || currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) { @@ -1678,7 +1671,7 @@ public class Loan extends AbstractPersistable { public LocalDate possibleNextRepaymentDate() { LocalDate earliestUnpaidInstallmentDate = new LocalDate(); - for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { if (installment.isNotFullyPaidOff()) { earliestUnpaidInstallmentDate = installment.getDueDate(); break; @@ -1686,7 +1679,7 @@ public class Loan extends AbstractPersistable { } LocalDate lastTransactionDate = null; - for (LoanTransaction transaction : this.loanTransactions) { + for (final LoanTransaction transaction : this.loanTransactions) { if (transaction.isRepayment() && transaction.isNonZero()) { lastTransactionDate = transaction.getTransactionDate(); } @@ -1701,10 +1694,10 @@ public class Loan extends AbstractPersistable { } public Money possibleNextRepaymentAmount() { - MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getPrincipal().getCurrency(); + final MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getPrincipal().getCurrency(); Money possibleNextRepaymentAmount = Money.zero(currency); - for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { if (installment.isNotFullyPaidOff()) { possibleNextRepaymentAmount = installment.getTotalOutstanding(currency); break; @@ -1723,7 +1716,7 @@ public class Loan extends AbstractPersistable { if (totalInterestOutstanding.isGreaterThanZero()) { // find earliest known instance of overdue interest and default to // that - for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { final Money outstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency()); if (scheduledRepayment.isOverdueOn(new LocalDate()) && scheduledRepayment.isNotFullyPaidOff() @@ -1762,7 +1755,7 @@ public class Loan extends AbstractPersistable { if (isClosedWrittenOff()) { // find write off transaction and reverse it - LoanTransaction writeOffTransaction = findWriteOffTransaction(); + final LoanTransaction writeOffTransaction = findWriteOffTransaction(); writeOffTransaction.reverse(); } @@ -1781,7 +1774,7 @@ public class Loan extends AbstractPersistable { private LoanTransaction findWriteOffTransaction() { LoanTransaction writeOff = null; - for (LoanTransaction transaction : this.loanTransactions) { + for (final LoanTransaction transaction : this.loanTransactions) { if (!transaction.isReversed() && transaction.isWriteOff()) { writeOff = transaction; } @@ -1802,7 +1795,7 @@ public class Loan extends AbstractPersistable { Money cumulativeTotalPaidOnInstallments = Money.zero(currency); Money cumulativeTotalWaivedOnInstallments = Money.zero(currency); - for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { cumulativeTotalPaidOnInstallments = cumulativeTotalPaidOnInstallments .plus(scheduledRepayment.getPrincipalCompleted(currency).plus(scheduledRepayment.getInterestPaid(currency))) @@ -1836,6 +1829,7 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed("transactionDate"); + final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); this.closedOnDate = writtenOffOnLocalDate.toDate(); this.writtenOffOnDate = writtenOffOnLocalDate.toDate(); @@ -1843,7 +1837,7 @@ public class Loan extends AbstractPersistable { changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("writtenOffOnDate", command.stringValueOfParameterNamed("transactionDate")); - if (writtenOffOnLocalDate.isBefore(this.getDisbursementDate())) { + if (writtenOffOnLocalDate.isBefore(getDisbursementDate())) { final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: " + getDisbursementDate().toString(); throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.before.submittal.date", errorMessage, @@ -1855,8 +1849,8 @@ public class Loan extends AbstractPersistable { throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate); } - loanTransaction = LoanTransaction.writeoff(this, writtenOffOnLocalDate); - final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, loanTransactions); + loanTransaction = LoanTransaction.writeoff(this, writtenOffOnLocalDate, txnExternalId); + final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions); if (!isLastTransaction) { final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions."; throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage, @@ -1882,11 +1876,12 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final LocalDate closureDate = command.localDateValueOfParameterNamed("transactionDate"); + final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); this.closedOnDate = closureDate.toDate(); changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate")); - if (closureDate.isBefore(this.getDisbursementDate())) { + if (closureDate.isBefore(getDisbursementDate())) { final String errorMessage = "The date on which a loan is closed cannot be before the loan disbursement date: " + getDisbursementDate().toString(); throw new InvalidLoanStateTransitionException("close", "cannot.be.before.submittal.date", errorMessage, closureDate, @@ -1911,8 +1906,8 @@ public class Loan extends AbstractPersistable { } this.closedOnDate = closureDate.toDate(); - loanTransaction = LoanTransaction.writeoff(this, closureDate); - boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, loanTransactions); + loanTransaction = LoanTransaction.writeoff(this, closureDate, txnExternalId); + final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions); if (!isLastTransaction) { final String errorMessage = "The closing date of the loan must be on or after latest transaction date."; throw new InvalidLoanStateTransitionException("close.loan", "must.occur.on.or.after.latest.transaction.date", @@ -1976,8 +1971,8 @@ public class Loan extends AbstractPersistable { changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("rescheduledOnDate", command.stringValueOfParameterNamed("transactionDate")); - LocalDate rescheduledOnLocalDate = new LocalDate(rescheduledOnDate); - if (rescheduledOnLocalDate.isBefore(this.getDisbursementDate())) { + final LocalDate rescheduledOnLocalDate = new LocalDate(this.rescheduledOnDate); + if (rescheduledOnLocalDate.isBefore(getDisbursementDate())) { final String errorMessage = "The date on which a loan is rescheduled cannot be before the loan disbursement date: " + getDisbursementDate().toString(); throw new InvalidLoanStateTransitionException("close.reschedule", "cannot.be.before.submittal.date", errorMessage, @@ -2004,7 +1999,7 @@ public class Loan extends AbstractPersistable { } private boolean isNotDisbursed() { - return !this.isDisbursed(); + return !isDisbursed(); } private boolean isDisbursed() { @@ -2012,7 +2007,7 @@ public class Loan extends AbstractPersistable { } private boolean isClosed() { - return status().isClosed() || this.isCancelled(); + return status().isClosed() || isCancelled(); } private boolean isClosedObligationsMet() { @@ -2028,7 +2023,7 @@ public class Loan extends AbstractPersistable { } private boolean isCancelled() { - return this.isRejected() || this.isWithdrawn(); + return isRejected() || isWithdrawn(); } private boolean isWithdrawn() { @@ -2045,7 +2040,7 @@ public class Loan extends AbstractPersistable { private boolean hasDisbursementTransaction() { boolean hasRepaymentTransaction = false; - for (LoanTransaction loanTransaction : this.loanTransactions) { + for (final LoanTransaction loanTransaction : this.loanTransactions) { if (loanTransaction.isDisbursement() && loanTransaction.isNotReversed()) { hasRepaymentTransaction = true; break; @@ -2055,7 +2050,7 @@ public class Loan extends AbstractPersistable { } public boolean isSubmittedOnDateAfter(final LocalDate compareDate) { - return this.submittedOnDate == null ? false : (new LocalDate(submittedOnDate)).isAfter(compareDate); + return this.submittedOnDate == null ? false : new LocalDate(this.submittedOnDate).isAfter(compareDate); } public LocalDate getSubmittedOnDate() { @@ -2110,7 +2105,7 @@ public class Loan extends AbstractPersistable { private Money getTotalPaidInRepayments() { Money cumulativePaid = Money.zero(loanCurrency()); - for (LoanTransaction repayment : this.loanTransactions) { + for (final LoanTransaction repayment : this.loanTransactions) { if (repayment.isRepayment()) { cumulativePaid = cumulativePaid.plus(repayment.getAmount(loanCurrency())); } @@ -2122,7 +2117,7 @@ public class Loan extends AbstractPersistable { private Money getTotalInterestOutstandingOnLoan() { Money cumulativeInterest = Money.zero(loanCurrency()); - for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency())); } @@ -2133,7 +2128,7 @@ public class Loan extends AbstractPersistable { private Money getTotalInterestOverdueOnLoan() { Money cumulativeInterestOverdue = Money.zero(this.loanRepaymentScheduleDetail.getPrincipal().getCurrency()); - for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { final Money interestOutstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency()); if (scheduledRepayment.isOverdueOn(new LocalDate())) { @@ -2149,7 +2144,7 @@ public class Loan extends AbstractPersistable { } public boolean hasIdentifyOf(final Long loanId) { - return loanId.equals(this.getId()); + return loanId.equals(getId()); } public boolean hasLoanOfficer(final Staff fromLoanOfficer) { @@ -2194,7 +2189,7 @@ public class Loan extends AbstractPersistable { final LoanOfficerAssignmentHistory lastAssignmentRecord = findLastAssignmentHistoryRecord(newLoanOfficer); // assignment date should not be less than loan submitted date - if (this.isSubmittedOnDateAfter(assignmentDate)) { + if (isSubmittedOnDateAfter(assignmentDate)) { final String errorMessage = "The Loan Officer assignment date (" + assignmentDate.toString() + ") cannot be before loan submitted date (" + getSubmittedOnDate().toString() + ")."; @@ -2222,8 +2217,8 @@ public class Loan extends AbstractPersistable { latestHistoryRecord.updateLoanOfficer(newLoanOfficer); this.loanOfficer = newLoanOfficer; } else if (latestHistoryRecord != null && latestHistoryRecord.hasStartDateBefore(assignmentDate)) { - final String errorMessage = "Loan with identifier " + this.getId() + " was already assigned before date " + assignmentDate; - throw new LoanOfficerAssignmentDateException("is.before.last.assignment.date", errorMessage, this.getId(), assignmentDate); + final String errorMessage = "Loan with identifier " + getId() + " was already assigned before date " + assignmentDate; + throw new LoanOfficerAssignmentDateException("is.before.last.assignment.date", errorMessage, getId(), assignmentDate); } else { if (latestHistoryRecord != null) { // loan officer correctly changed from previous loan officer to @@ -2232,7 +2227,7 @@ public class Loan extends AbstractPersistable { } this.loanOfficer = newLoanOfficer; - if (this.isNotSubmittedAndPendingApproval()) { + if (isNotSubmittedAndPendingApproval()) { final LoanOfficerAssignmentHistory loanOfficerAssignmentHistory = LoanOfficerAssignmentHistory.createNew(this, this.loanOfficer, assignmentDate); this.loanOfficerHistory.add(loanOfficerAssignmentHistory); @@ -2261,8 +2256,7 @@ public class Loan extends AbstractPersistable { final String errorMessage = "The Loan officer Unassign date(" + unassignDate + ") cannot be before its assignment date (" + latestHistoryRecord.getStartDate() + ")."; - throw new LoanOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, this.getId(), this - .getLoanOfficer().getId(), latestHistoryRecord.getStartDate(), unassignDate); + throw new LoanOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, getId(), getLoanOfficer().getId(), latestHistoryRecord.getStartDate(), unassignDate); } else if (unassignDate.isAfter(today)) { @@ -2275,7 +2269,7 @@ public class Loan extends AbstractPersistable { private LoanOfficerAssignmentHistory findLatestIncompleteHistoryRecord() { LoanOfficerAssignmentHistory latestRecordWithNoEndDate = null; - for (LoanOfficerAssignmentHistory historyRecord : this.loanOfficerHistory) { + for (final LoanOfficerAssignmentHistory historyRecord : this.loanOfficerHistory) { if (historyRecord.isCurrentRecord()) { latestRecordWithNoEndDate = historyRecord; break; @@ -2287,7 +2281,7 @@ public class Loan extends AbstractPersistable { private LoanOfficerAssignmentHistory findLastAssignmentHistoryRecord(final Staff newLoanOfficer) { LoanOfficerAssignmentHistory lastAssignmentRecordLatestEndDate = null; - for (LoanOfficerAssignmentHistory historyRecord : this.loanOfficerHistory) { + for (final LoanOfficerAssignmentHistory historyRecord : this.loanOfficerHistory) { if (historyRecord.isCurrentRecord() && !historyRecord.isSameLoanOfficer(newLoanOfficer)) { lastAssignmentRecordLatestEndDate = historyRecord; @@ -2358,15 +2352,15 @@ public class Loan extends AbstractPersistable { final List existingReversedTransactionIds) { final Map accountingBridgeData = new LinkedHashMap(); - accountingBridgeData.put("loanId", this.getId()); - accountingBridgeData.put("loanProductId", this.productId()); - accountingBridgeData.put("officeId", this.getOfficeId()); + accountingBridgeData.put("loanId", getId()); + accountingBridgeData.put("loanProductId", productId()); + accountingBridgeData.put("officeId", getOfficeId()); accountingBridgeData.put("calculatedInterest", this.summary.getTotalInterestCharged()); - accountingBridgeData.put("cashBasedAccountingEnabled", this.isCashBasedAccountingEnabledOnLoanProduct()); - accountingBridgeData.put("accrualBasedAccountingEnabled", this.isAccrualBasedAccountingEnabledOnLoanProduct()); + accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnLoanProduct()); + accountingBridgeData.put("accrualBasedAccountingEnabled", isAccrualBasedAccountingEnabledOnLoanProduct()); final List> newLoanTransactions = new ArrayList>(); - for (LoanTransaction transaction : this.loanTransactions) { + for (final LoanTransaction transaction : this.loanTransactions) { if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) { newLoanTransactions.add(transaction.toMapData(currencyData)); } else if (!existingTransactionIds.contains(transaction.getId())) { @@ -2395,41 +2389,46 @@ public class Loan extends AbstractPersistable { public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { - + //first repayment's from date is same as disbursement date. LocalDate tmpFromDate = this.getDisbursementDate(); final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType(); final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery(); - final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType); + final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType); LocalDate newRepaymentDate = null; - for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); // FIXME: AA this won't update repayment dates before current date. if (oldDueDate.isAfter(meetingStartDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) { - - newRepaymentDate = CalendarHelper.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate, + newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate, loanRepaymentInterval, frequency, workingDays); final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType, loanRepaymentInterval, tmpFromDate); if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) { - newRepaymentDate = CalendarHelper.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate, + newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate, loanRepaymentInterval, frequency, workingDays); } if (isHolidayEnabled) { - newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays); + // reset repayment date with new meeting date + newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate, + loanRepaymentInterval, frequency, workingDays); + if (isHolidayEnabled) { + newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays); + } + + loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate); + // reset from date to get actual daysInPeriod + loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); + tmpFromDate = newRepaymentDate;// update with new repayment + // date + } else { + tmpFromDate = oldDueDate; } - loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate); - // reset from date to get actual daysInPeriod - loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); - tmpFromDate = newRepaymentDate;// update with new repayment date - } else { - tmpFromDate = oldDueDate; } - } } @@ -2458,9 +2457,9 @@ public class Loan extends AbstractPersistable { public void applyHolidayToRepaymentScheduleDates(final Holiday holiday) { // first repayment's from date is same as disbursement date. - LocalDate tmpFromDate = this.getDisbursementDate(); + LocalDate tmpFromDate = getDisbursementDate(); // Loop through all loanRepayments - for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { + for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); // update from date if it's not same as previous installament's due @@ -2468,10 +2467,12 @@ public class Loan extends AbstractPersistable { if (!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)) { loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); } - if (oldDueDate.isAfter(holiday.getToDateLocalDate())) break; + if (oldDueDate.isAfter(holiday.getToDateLocalDate())) { + break; + } if (oldDueDate.equals(holiday.getFromDateLocalDate()) || oldDueDate.equals(holiday.getToDateLocalDate()) - || (oldDueDate.isAfter(holiday.getFromDateLocalDate()) && oldDueDate.isBefore(holiday.getToDateLocalDate()))) { + || oldDueDate.isAfter(holiday.getFromDateLocalDate()) && oldDueDate.isBefore(holiday.getToDateLocalDate())) { //FIXME: AA do we need to apply non-working days. //Assuming holiday's repayment reschedule to date cannot be created on a non-working day. final LocalDate newRepaymentDate = holiday.getRepaymentsRescheduledToLocalDate(); @@ -2483,9 +2484,10 @@ public class Loan extends AbstractPersistable { private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { if (!allowTransactionsOnNonWorkingDay) { - if(!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())){ + if (!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())) { final String errorMessage = "Expected disbursement date cannot be on a non working day"; - throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, getExpectedDisbursedOnLocalDate()); + throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, + getExpectedDisbursedOnLocalDate()); } } } @@ -2494,12 +2496,11 @@ public class Loan extends AbstractPersistable { if (!allowTransactionsOnHoliday) { if (HolidayUtil.isHoliday(getDisbursementDate(), holidays)) { final String errorMessage = "Expected disbursement date cannot be on a holiday"; - throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage, - getExpectedDisbursedOnLocalDate()); + throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage, getExpectedDisbursedOnLocalDate()); } } } - + private void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { if (!allowTransactionsOnNonWorkingDay) { @@ -2518,12 +2519,16 @@ public class Loan extends AbstractPersistable { throw new LoanApplicationDateException("repayment.date.on.holiday", errorMessage, repaymentDate); } } - } + } public Group group() { return this.group; } + public void updateGroup(Group newGroup) { + this.group = newGroup; + } + public Integer getCurrentLoanCounter() { return this.loanCounter; } @@ -2541,7 +2546,7 @@ public class Loan extends AbstractPersistable { } public boolean isGroupLoan() { - return LoanType.fromInt(this.loanType).isGroupLoan(); + return AccountType.fromInt(this.loanType).isGroupAccount(); } public void updateInterestRateFrequencyType() { @@ -2564,8 +2569,5 @@ public class Loan extends AbstractPersistable { } - public void updateGroup(Group group) { - this.group = group; - } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java new file mode 100644 index 000000000..ef82156a8 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.loanaccount.domain; + +import java.math.BigDecimal; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; + +public interface LoanAccountDomainService { + + LoanTransaction makeRepayment(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, + BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java new file mode 100644 index 000000000..6bbb5ee16 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -0,0 +1,166 @@ +/** + * 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.loanaccount.domain; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.organisation.holiday.domain.Holiday; +import org.mifosplatform.organisation.holiday.domain.HolidayRepository; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.organisation.workingdays.domain.WorkingDays; +import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; +import org.mifosplatform.portfolio.group.domain.Group; +import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; +import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; +import org.mifosplatform.portfolio.note.domain.Note; +import org.mifosplatform.portfolio.note.domain.NoteRepository; +import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { + + private final LoanAssembler loanAccountAssembler; + private final LoanRepository loanRepository; + private final LoanTransactionRepository loanTransactionRepository; + private final ConfigurationDomainService configurationDomainService; + private final HolidayRepository holidayRepository; + private final WorkingDaysRepositoryWrapper workingDaysRepository; + + private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; + private final JournalEntryWritePlatformService journalEntryWritePlatformService; + private final NoteRepository noteRepository; + + @Autowired + public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, + final LoanTransactionRepository loanTransactionRepository, final NoteRepository noteRepository, + final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, + final WorkingDaysRepositoryWrapper workingDaysRepository, + final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, + final JournalEntryWritePlatformService journalEntryWritePlatformService) { + this.loanAccountAssembler = loanAccountAssembler; + this.loanRepository = loanRepository; + this.loanTransactionRepository = loanTransactionRepository; + this.noteRepository = noteRepository; + this.configurationDomainService = configurationDomainService; + this.holidayRepository = holidayRepository; + this.workingDaysRepository = workingDaysRepository; + this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; + } + + @Transactional + @Override + public LoanTransaction makeRepayment(final Long accountId, final CommandProcessingResultBuilder builderResult, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, + final String txnExternalId) { + + final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); + checkClientOrGroupActive(loan); + + // TODO: Is it required to validate transaction date with meeting dates + // if repayments is synced with meeting? + /* + * if(loan.isSyncDisbursementWithMeeting()){ // validate actual + * disbursement date against meeting date CalendarInstance + * calendarInstance = + * this.calendarInstanceRepository.findCalendarInstaneByLoanId + * (loan.getId(), CalendarEntityType.LOANS.getValue()); + * this.loanEventApiJsonValidator + * .validateRepaymentDateWithMeetingDate(transactionDate, + * calendarInstance); } + */ + + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount); + final LoanTransaction newRepaymentTransaction = LoanTransaction.repayment(repaymentAmount, paymentDetail, transactionDate, + txnExternalId); + final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); + final List holidays = this.holidayRepository + .findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), transactionDate.toDate()); + final WorkingDays workingDays = this.workingDaysRepository.findOne(); + final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); + + final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, + defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday, + holidays, workingDays, allowTransactionsOnNonWorkingDay); + + this.loanTransactionRepository.save(newRepaymentTransaction); + + /*** + * TODO Vishwas Batch save is giving me a + * HibernateOptimisticLockingFailureException, looping and saving for + * the time being, not a major issue for now as this loop is entered + * only in edge cases (when a payment is made before the latest payment + * recorded against the loan) + ***/ + if (changedTransactionDetail != null) { + for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { + this.loanTransactionRepository.save(loanTransaction); + } + } + this.loanRepository.save(loan); + + if (StringUtils.isNotBlank(noteText)) { + final Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText); + this.noteRepository.save(note); + } + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + + builderResult.withEntityId(newRepaymentTransaction.getId()) // + .withOfficeId(loan.getOfficeId()) // + .withClientId(loan.getClientId()) // + .withGroupId(loan.getGroupId()); // + + return newRepaymentTransaction; + } + + private void postJournalEntries(final Loan loanAccount, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + final MonetaryCurrency currency = loanAccount.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); + + final Map accountingBridgeData = loanAccount.deriveAccountingBridgeData(applicationCurrency.toData(), + existingTransactionIds, existingReversedTransactionIds); + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + } + + private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { + final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); + return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); + } + + private void checkClientOrGroupActive(final Loan loan) { + final Client client = loan.client(); + if (client != null) { + if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); } + } + final Group group = loan.group(); + if (group != null) { + if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); } + } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java index e56956ade..bcad5845d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanOfficerAssignmentHistory.java @@ -25,7 +25,6 @@ import org.mifosplatform.useradministration.domain.AppUser; @Table(name = "m_loan_officer_assignment_history") public class LoanOfficerAssignmentHistory extends AbstractAuditableCustom { - @SuppressWarnings("unused") @ManyToOne @JoinColumn(name = "loan_id", nullable = false) private Loan loan; @@ -74,7 +73,7 @@ public class LoanOfficerAssignmentHistory extends AbstractAuditableCustom, JpaSpecificat public static final String FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP = "Select loan.loanProduct.id from Loan loan where " + "loan.group.id = :groupId and loan.loanStatus = :loanStatus and loan.client.id is NULL group by loan.loanProduct.id"; + public static final String DOES_CLIENT_HAVE_ACTIVE_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.client.id = :clientId and loan.loanStatus in (100,200,300)"; + @Query(FIND_GROUP_LOANS_DISBURSED_AFTER) List getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId, @Param("loanType") Integer loanType); @@ -101,15 +103,21 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat List getLoansDisbursedAfter(@Param("disbursalDate") Date disbursalDate); @Query("from Loan loan where loan.client.office.id IN :officeIds and loan.loanStatus IN :loanStatuses") - List findByClientOfficeIdsAndLoanStatus(@Param("officeIds") Collection officeIds, @Param("loanStatuses") Collection loanStatuses); - - @Query("from Loan loan where loan.group.office.id IN :officeIds and loan.loanStatus IN :loanStatuses") - List findByGroupOfficeIdsAndLoanStatus(@Param("officeIds") Collection officeIds, @Param("loanStatuses") Collection loanStatuses); + List findByClientOfficeIdsAndLoanStatus(@Param("officeIds") Collection officeIds, + @Param("loanStatuses") Collection loanStatuses); + @Query("from Loan loan where loan.group.office.id IN :officeIds and loan.loanStatus IN :loanStatuses") + List findByGroupOfficeIdsAndLoanStatus(@Param("officeIds") Collection officeIds, + @Param("loanStatuses") Collection loanStatuses); + + /*** FIXME: Add more appropriate names for the query ***/ @Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_CLIENT) List findActiveLoansLoanProductIdsByClient(@Param("clientId") Long clientId, @Param("loanStatus") Integer loanStatus); @Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP) List findActiveLoansLoanProductIdsByGroup(@Param("groupId") Long groupId, @Param("loanStatus") Integer loanStatus); + @Query(DOES_CLIENT_HAVE_ACTIVE_LOANS) + boolean doesClientHaveActiveLoans(@Param("clientId") Long clientId); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java index abf9b99a9..958a551f2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java @@ -111,4 +111,8 @@ public enum LoanStatus { public boolean isRejected() { return this.value.equals(LoanStatus.REJECTED.getValue()); } + + public boolean isActiveOrAwaitingApprovalOrDisbursal() { + return this.isApproved() || this.isSubmittedAndPendingApproval() || this.isActive(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java index df99ab781..98ffcc58a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java @@ -84,27 +84,21 @@ public final class LoanSummary { @Column(name = "penalty_charges_outstanding_derived", scale = 6, precision = 19) private BigDecimal totalPenaltyChargesOutstanding; - @SuppressWarnings("unused") @Column(name = "total_expected_repayment_derived", scale = 6, precision = 19) private BigDecimal totalExpectedRepayment; - @SuppressWarnings("unused") @Column(name = "total_repayment_derived", scale = 6, precision = 19) private BigDecimal totalRepayment; - @SuppressWarnings("unused") @Column(name = "total_expected_costofloan_derived", scale = 6, precision = 19) private BigDecimal totalExpectedCostOfLoan; - @SuppressWarnings("unused") @Column(name = "total_costofloan_derived", scale = 6, precision = 19) private BigDecimal totalCostOfLoan; - @SuppressWarnings("unused") @Column(name = "total_waived_derived", scale = 6, precision = 19) private BigDecimal totalWaived; - @SuppressWarnings("unused") @Column(name = "total_writtenoff_derived", scale = 6, precision = 19) private BigDecimal totalWrittenOff; @@ -185,7 +179,7 @@ public final class LoanSummary { this.totalPrincipalWrittenOff = summaryWrapper.calculateTotalPrincipalWrittenOff(repaymentScheduleInstallments, currency) .getAmount(); - this.totalPrincipalOutstanding = principal.minus(totalPrincipalRepaid).minus(totalPrincipalWrittenOff).getAmount(); + this.totalPrincipalOutstanding = principal.minus(this.totalPrincipalRepaid).minus(this.totalPrincipalWrittenOff).getAmount(); final Money totalInterestCharged = summaryWrapper.calculateTotalInterestCharged(repaymentScheduleInstallments, currency); this.totalInterestCharged = totalInterestCharged.getAmount(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummaryArrearsAging.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummaryArrearsAging.java index 1ea250789..8f3e08d7e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummaryArrearsAging.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummaryArrearsAging.java @@ -63,12 +63,10 @@ public final class LoanSummaryArrearsAging implements Persistable { @Column(name = "total_overdue_derived", scale = 6, precision = 19) private BigDecimal totalOverdue; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "overdue_since_date_derived") private Date overdueSinceDate; - @SuppressWarnings("unused") @OneToOne(optional = false) @PrimaryKeyJoinColumn private Loan loan; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 56b549ea0..c39b04fa1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -23,6 +23,7 @@ import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; @@ -42,7 +43,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable; * Disbursements, Repayments, Waivers, Write-off etc */ @Entity -@Table(name = "m_loan_transaction") +@Table(name = "m_loan_transaction", uniqueConstraints = {@UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") }) public final class LoanTransaction extends AbstractPersistable { @ManyToOne(optional = false) @@ -78,6 +79,9 @@ public final class LoanTransaction extends AbstractPersistable { @Column(name = "is_reversed", nullable = false) private boolean reversed; + @Column(name = "external_id", length = 100, nullable = true, unique = true) + private String externalId; + @LazyCollection(LazyCollectionOption.FALSE) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", orphanRemoval = true) private Set loanChargesPaid = new HashSet(); @@ -88,24 +92,24 @@ public final class LoanTransaction extends AbstractPersistable { this.typeOf = null; } - public static LoanTransaction disbursement(final Money amount, final PaymentDetail paymentDetail, final LocalDate disbursementDate) { - return new LoanTransaction(null, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate); + public static LoanTransaction disbursement(final Money amount, final PaymentDetail paymentDetail, final LocalDate disbursementDate, final String externalId) { + return new LoanTransaction(null, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate, externalId); } - public static LoanTransaction repayment(final Money amount, final PaymentDetail paymentDetail, final LocalDate paymentDate) { - return new LoanTransaction(null, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate); + public static LoanTransaction repayment(final Money amount, final PaymentDetail paymentDetail, final LocalDate paymentDate, final String externalId) { + return new LoanTransaction(null, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId); } - public static LoanTransaction repaymentAtDisbursement(final Money amount, final PaymentDetail paymentDetail, final LocalDate paymentDate) { - return new LoanTransaction(null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(), paymentDate); + public static LoanTransaction repaymentAtDisbursement(final Money amount, final PaymentDetail paymentDetail, final LocalDate paymentDate, final String externalId) { + return new LoanTransaction(null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(), paymentDate, externalId); } public static LoanTransaction waiver(final Loan loan, final Money waived, final LocalDate waiveDate) { - return new LoanTransaction(loan, LoanTransactionType.WAIVE_INTEREST, waived.getAmount(), waiveDate); + return new LoanTransaction(loan, LoanTransactionType.WAIVE_INTEREST, waived.getAmount(), waiveDate, null); } public static LoanTransaction applyInterest(final Loan loan, final Money amount, final LocalDate interestAppliedDate) { - return new LoanTransaction(loan, LoanTransactionType.APPLY_INTEREST, amount.getAmount(), interestAppliedDate); + return new LoanTransaction(loan, LoanTransactionType.APPLY_INTEREST, amount.getAmount(), interestAppliedDate, null); } public static LoanTransaction copyTransactionProperties(LoanTransaction loanTransaction) { @@ -116,7 +120,7 @@ public final class LoanTransaction extends AbstractPersistable { public static LoanTransaction applyLoanCharge(final Loan loan, final Money amount, final LocalDate applyDate, final Money feeCharges, final Money penaltyCharges) { - LoanTransaction applyCharge = new LoanTransaction(loan, LoanTransactionType.APPLY_CHARGES, amount.getAmount(), applyDate); + LoanTransaction applyCharge = new LoanTransaction(loan, LoanTransactionType.APPLY_CHARGES, amount.getAmount(), applyDate, null); applyCharge.updateChargesComponents(feeCharges, penaltyCharges); return applyCharge; } @@ -149,30 +153,32 @@ public final class LoanTransaction extends AbstractPersistable { public static LoanTransaction waiveLoanCharge(final Loan loan, final Money waived, final LocalDate waiveDate, final Money feeChargesWaived, final Money penaltyChargesWaived) { - LoanTransaction waiver = new LoanTransaction(loan, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate); + LoanTransaction waiver = new LoanTransaction(loan, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate, null); waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived); return waiver; } - public static LoanTransaction writeoff(final Loan loan, final LocalDate writeOffDate) { - return new LoanTransaction(loan, LoanTransactionType.WRITEOFF, null, writeOffDate); + public static LoanTransaction writeoff(final Loan loan, final LocalDate writeOffDate, final String externalId) { + return new LoanTransaction(loan, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId); } - private LoanTransaction(final Loan loan, final LoanTransactionType type, final BigDecimal amount, final LocalDate date) { + private LoanTransaction(final Loan loan, final LoanTransactionType type, final BigDecimal amount, final LocalDate date, final String externalId) { this.loan = loan; this.typeOf = type.getValue(); this.amount = amount; this.dateOf = date.toDateMidnight().toDate(); + this.externalId = externalId; } private LoanTransaction(final Loan loan, final LoanTransactionType type, final PaymentDetail paymentDetail, final BigDecimal amount, - final LocalDate date) { + final LocalDate date, final String externalId) { this.loan = loan; this.typeOf = type.getValue(); this.paymentDetail = paymentDetail; this.amount = amount; this.dateOf = date.toDateMidnight().toDate(); + this.externalId = externalId; } public void reverse() { @@ -353,7 +359,7 @@ public final class LoanTransaction extends AbstractPersistable { paymentDetailData = paymentDetail.toData(); } return new LoanTransactionData(this.getId(), transactionType, paymentDetailData, currencyData, getTransactionDate(), this.amount, - this.principalPortion, this.interestPortion, this.feeChargesPortion, this.penaltyChargesPortion); + this.principalPortion, this.interestPortion, this.feeChargesPortion, this.penaltyChargesPortion, this.externalId); } public Map toMapData(final CurrencyData currencyData) { @@ -405,4 +411,8 @@ public final class LoanTransaction extends AbstractPersistable { this.loanChargesPaid = loanChargesPaid; } + public String getExternalId() { + return externalId; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanType.java deleted file mode 100644 index 9a51a5cc6..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanType.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * 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.loanaccount.domain; - -/** - * Enum representation of loan types . - */ -public enum LoanType { - - INVALID(0, "loanType.invalid"), // - INDIVIDUAL(1, "loanType.individual"), // - GROUP(2, "loanType.group"), // - JLG(3, "loanType.jlg");//JLG loans given in group context - - private final Integer value; - private final String code; - - private LoanType(final Integer value, final String code) { - this.value = value; - this.code = code; - } - - public static LoanType fromInt(final Integer loanTypeValue) { - - LoanType enumeration = LoanType.INVALID; - switch (loanTypeValue) { - case 1: - enumeration = LoanType.INDIVIDUAL; - break; - case 2: - enumeration = LoanType.GROUP; - break; - case 3: - enumeration = LoanType.JLG; - break; - } - return enumeration; - } - - public static LoanType fromName(final String name){ - LoanType loanType = LoanType.INVALID; - for (LoanType type : LoanType.values()) { - if(type.getName().equals(name)) { - loanType = type; - break; - } - } - return loanType; - } - - public Integer getValue() { - return this.value; - } - - public String getCode() { - return code; - } - - public String getName(){ - return name().toLowerCase(); - } - - public boolean isInvalid(){ - return this.value.equals(LoanType.INVALID.getValue()); - } - - public boolean isIndividualLoan(){ - return this.value.equals(LoanType.INDIVIDUAL.getValue()); - } - - public boolean isGroupLoan(){ - return this.value.equals(LoanType.GROUP.getValue()); - } - - public boolean isJLGLoan(){ - return this.value.equals(LoanType.JLG.getValue()); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 552f19e3b..0286f054b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -157,7 +157,7 @@ public final class LoanApplicationTerms { this.expectedDisbursementDate = expectedDisbursementDate; this.repaymentsStartingFromDate = repaymentsStartingFromDate; this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; - + this.principalGrace = principalGrace; this.interestPaymentGrace = interestPaymentGrace; this.interestChargingGrace = interestChargingGrace; @@ -172,7 +172,15 @@ public final class LoanApplicationTerms { Money adjusted = principalForPeriod; // adjust cumulative fields for principal & interest if needed - if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) { + if (this.principal.getAmount().subtract(totalCumulativePrincipalToDate.getAmount()).add(principalForPeriod.getAmount()).compareTo(principalForPeriod.getAmount()) == -1) { + adjusted = adjusted.minus(adjusted); + MonetaryCurrency monetaryCurrency = new MonetaryCurrency(adjusted.getCurrencyCode(), 0, adjusted.getCurrencyInMultiplesOf()); + BigDecimal diff = this.principal.getAmount().subtract(totalCumulativePrincipalToDate.getAmount()).add(principalForPeriod.getAmount()); + if(diff.doubleValue()<0){ + diff = BigDecimal.ZERO; + } + adjusted = adjusted.plus(Money.of(monetaryCurrency,diff)); + } else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) { final Money difference = totalCumulativePrincipalToDate.minus(this.principal); if (difference.isLessThanZero()) { @@ -189,9 +197,16 @@ public final class LoanApplicationTerms { final Money totalInterestDueForLoan, final int periodNumber) { Money adjusted = interestForThisPeriod; - - if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) { - + if (totalInterestDueForLoan.getAmount().subtract(totalCumulativeInterestToDate.getAmount()).add(interestForThisPeriod.getAmount()) + .compareTo(interestForThisPeriod.getAmount()) == -1) { + adjusted = adjusted.minus(adjusted); + MonetaryCurrency monetaryCurrency = new MonetaryCurrency(adjusted.getCurrencyCode(), 0, adjusted.getCurrencyInMultiplesOf()); + BigDecimal diff = totalInterestDueForLoan.getAmount().subtract(totalCumulativeInterestToDate.getAmount()).add(interestForThisPeriod.getAmount()); + if(diff.doubleValue()<0){ + diff = BigDecimal.ZERO; + } + adjusted = adjusted.plus(Money.of(monetaryCurrency,diff)); + } else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) { final Money interestDifference = totalCumulativeInterestToDate.minus(totalInterestDueForLoan); if (interestDifference.isLessThanZero()) { adjusted = interestForThisPeriod.plus(interestDifference.abs()); @@ -199,7 +214,9 @@ public final class LoanApplicationTerms { adjusted = interestForThisPeriod.minus(interestDifference.abs()); } } - + if (adjusted.isLessThanZero()) { + adjusted = adjusted.plus(adjusted); + } return adjusted; } @@ -618,7 +635,8 @@ public final class LoanApplicationTerms { } public LoanProductRelatedDetail toLoanProductRelatedDetail() { - final MonetaryCurrency currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces()); + final MonetaryCurrency currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces(), + this.currency.getCurrencyInMultiplesOf()); return LoanProductRelatedDetail.createFrom(currency, this.principal.getAmount(), this.interestRatePerPeriod, this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod, @@ -690,7 +708,7 @@ public final class LoanApplicationTerms { public LocalDate getCalculatedRepaymentsStartingFromLocalDate() { return this.calculatedRepaymentsStartingFromDate; } - + public Money getPrincipal() { return this.principal; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java index 495fcdbea..c337a0f50 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java @@ -62,7 +62,8 @@ public final class LoanScheduleModel { public LoanScheduleData toData() { final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal(); - final CurrencyData currency = applicationCurrency.toData(decimalPlaces); + final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf(); + final CurrencyData currency = applicationCurrency.toData(decimalPlaces,inMultiplesOf); final Collection periodsData = new ArrayList(); for (LoanScheduleModelPeriod modelPeriod : this.periods) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 367694e52..9634c8be5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -28,7 +28,7 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchException; -import org.mifosplatform.portfolio.calendar.service.CalendarHelper; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; import org.mifosplatform.portfolio.group.domain.Group; @@ -149,9 +149,9 @@ public class LoanScheduleAssembler { if (calendar == null) { throw new CalendarNotFoundException(calendarId); } // validate repayment frequency and interval with meeting frequency and interval - PeriodFrequencyType meetingPeriodFrequency = CalendarHelper.getMeetingPeriodFrequencyType(calendar.getRecurrence()); + PeriodFrequencyType meetingPeriodFrequency = CalendarUtils.getMeetingPeriodFrequencyType(calendar.getRecurrence()); validateRepaymentFrequencyIsSameAsMeetingFrequency(meetingPeriodFrequency.getValue(), repaymentFrequencyType, - CalendarHelper.getInterval(calendar.getRecurrence()), repaymentEvery); + CalendarUtils.getInterval(calendar.getRecurrence()), repaymentEvery); } if (synchDisbursement != null && synchDisbursement.booleanValue()) { @@ -166,8 +166,8 @@ public class LoanScheduleAssembler { if (calculatedRepaymentsStartingFromDate == null) { // FIXME: AA - Possibility of having next meeting date immediately after disbursement date, // need to have minimum number of days gap between disbursement and first repayment date. - final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType); - calculatedRepaymentsStartingFromDate = CalendarHelper.getFirstRepaymentMeetingDate(calendar, expectedDisbursementDate, + final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType); + calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, expectedDisbursementDate, repaymentEvery, frequency); } else {// validate user provided repaymentsStartFromDate validateRepaymentsStartDateWithMeetingDates(repaymentsStartingFromDate, calendar); @@ -192,7 +192,7 @@ public class LoanScheduleAssembler { private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) { if (repaymentsStartingFromDate != null - && !CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), + && !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentsStartingFromDate)) { final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date"; throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage, @@ -202,7 +202,7 @@ public class LoanScheduleAssembler { private void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar) { // disbursement date should fall on a meeting date - if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), expectedDisbursementDate)) { + if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), expectedDisbursementDate)) { final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date"; throw new LoanApplicationDateException("disbursement.date.do.not.match.meeting.date", errorMessage, expectedDisbursementDate); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index f826af108..48f44a7cc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -22,7 +22,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.loanaccount.domain.LoanType; +import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -77,22 +77,22 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(loanTypeParameterName).value(loanTypeStr).notNull(); if (!StringUtils.isBlank(loanTypeStr)) { - final LoanType loanType = LoanType.fromName(loanTypeStr); + final AccountType loanType = AccountType.fromName(loanTypeStr); baseDataValidator.reset().parameter(loanTypeParameterName).value(loanType.getValue()).inMinMaxRange(1, 3); final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element); final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element); - if (loanType.isIndividualLoan()) { + if (loanType.isIndividualAccount()) { baseDataValidator.reset().parameter("clientId").value(clientId).notNull().longGreaterThanZero(); baseDataValidator.reset().parameter("groupId").value(groupId).mustBeBlankWhenParameterProvided("clientId", clientId); } - if (loanType.isGroupLoan()) { + if (loanType.isGroupAccount()) { baseDataValidator.reset().parameter("groupId").value(groupId).notNull().longGreaterThanZero(); baseDataValidator.reset().parameter("clientId").value(clientId).mustBeBlankWhenParameterProvided("groupId", groupId); } - if (loanType.isJLGLoan()) { + if (loanType.isJLGAccount()) { baseDataValidator.reset().parameter("clientId").value(clientId).notNull().integerGreaterThanZero(); baseDataValidator.reset().parameter("groupId").value(groupId).notNull().longGreaterThanZero(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java index 65d983a2b..506a1e9f3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java @@ -24,7 +24,7 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException; -import org.mifosplatform.portfolio.calendar.service.CalendarHelper; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -50,7 +50,7 @@ public final class LoanEventApiJsonValidator { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - final Set disbursementParameters = new HashSet(Arrays.asList("actualDisbursementDate", "note", "locale", + final Set disbursementParameters = new HashSet(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber")); final Type typeOfMap = new TypeToken>() {}.getType(); @@ -76,7 +76,7 @@ public final class LoanEventApiJsonValidator { Calendar calendar = calendarInstance.getCalendar(); if (calendar != null && actualDisbursementDate != null) { // Disbursement date should fall on a meeting date - if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), actualDisbursementDate)) { + if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), actualDisbursementDate)) { final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString() + "' does not fall on a meeting date."; throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage, actualDisbursementDate.toString(), @@ -92,7 +92,7 @@ public final class LoanEventApiJsonValidator { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - final Set transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "note", + final Set transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "externalId", "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber")); final Type typeOfMap = new TypeToken>() {}.getType(); @@ -119,7 +119,7 @@ public final class LoanEventApiJsonValidator { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - final Set transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "note", + final Set transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "externalId", "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber")); final Type typeOfMap = new TypeToken>() {}.getType(); @@ -147,7 +147,7 @@ public final class LoanEventApiJsonValidator { Calendar calendar = calendarInstance.getCalendar(); if (calendar != null && repaymentDate != null) { // Disbursement date should fall on a meeting date - if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) { + if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) { final String errorMessage = "Transaction date '" + repaymentDate.toString() + "' does not fall on a meeting date."; throw new NotValidRecurringDateException("loan.transaction.date", errorMessage, repaymentDate.toString(), @@ -263,4 +263,30 @@ public final class LoanEventApiJsonValidator { throwExceptionIfValidationWarningsExist(dataValidationErrors); } + + public void validateForBulkLoanReassignment(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Set supportedParameters = new HashSet(Arrays.asList("assignmentDate", "fromLoanOfficerId", + "toLoanOfficerId", "loans", "locale", "dateFormat")); + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanOfficer"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final LocalDate assignmentDate = this.fromApiJsonHelper.extractLocalDateNamed("assignmentDate", element); + baseDataValidator.reset().parameter("assignmentDate").value(assignmentDate).notNull(); + final Long fromLoanOfficerId = this.fromApiJsonHelper.extractLongNamed("fromLoanOfficerId", element); + baseDataValidator.reset().parameter("fromLoanOfficerId").value(fromLoanOfficerId).notNull().longGreaterThanZero(); + final Long toLoanOfficerId = this.fromApiJsonHelper.extractLongNamed("toLoanOfficerId", element); + baseDataValidator.reset().parameter("toLoanOfficerId").value(toLoanOfficerId).notNull().longGreaterThanZero(); + final String[] loans = this.fromApiJsonHelper.extractArrayNamed("loans", element); + baseDataValidator.reset().parameter("loans").value(loans).arrayNotEmpty(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java index b0409c981..6e5d1c4de 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/BulkLoansReadPlatformServiceImpl.java @@ -14,10 +14,10 @@ import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.staff.data.StaffAccountSummaryCollectionData; -import org.mifosplatform.portfolio.client.data.ClientLoanAccountSummaryData; -import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; -import org.mifosplatform.portfolio.group.data.GroupAccountSummaryData; -import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; +import org.mifosplatform.portfolio.accountdetails.data.LoanAccountSummaryData; +import org.mifosplatform.portfolio.accountdetails.service.AccountDetailsReadPlatformService; +import org.mifosplatform.portfolio.client.domain.ClientStatus; +import org.mifosplatform.portfolio.group.domain.GroupingTypeStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; @@ -28,16 +28,14 @@ public class BulkLoansReadPlatformServiceImpl implements BulkLoansReadPlatformSe private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - private final ClientReadPlatformService clientReadPlatformService; - private final GroupReadPlatformService groupReadPlatformService; + private final AccountDetailsReadPlatformService accountDetailsReadPlatformService; @Autowired public BulkLoansReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, - final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService) { + final AccountDetailsReadPlatformService accountDetailsReadPlatformService) { this.context = context; this.jdbcTemplate = new JdbcTemplate(dataSource); - this.clientReadPlatformService = clientReadPlatformService; - this.groupReadPlatformService = groupReadPlatformService; + this.accountDetailsReadPlatformService = accountDetailsReadPlatformService; } @Override @@ -46,28 +44,28 @@ public class BulkLoansReadPlatformServiceImpl implements BulkLoansReadPlatformSe context.authenticatedUser(); final StaffClientMapper staffClientMapper = new StaffClientMapper(); - final String clientSql = "select distinct " + staffClientMapper.schema(); + final String clientSql = "select distinct " + staffClientMapper.schema() +" and c.status_enum=?"; final StaffGroupMapper staffGroupMapper = new StaffGroupMapper(); - final String groupSql = "select distinct " + staffGroupMapper.schema(); + final String groupSql = "select distinct " + staffGroupMapper.schema() +" and g.status_enum=?"; - final List clientSummaryList = this.jdbcTemplate.query(clientSql, - staffClientMapper, new Object[] { loanOfficerId }); + final List clientSummaryList = this.jdbcTemplate.query(clientSql, + staffClientMapper, new Object[] { loanOfficerId, ClientStatus.ACTIVE.getValue()}); - for (StaffAccountSummaryCollectionData.ClientSummary clientSummary : clientSummaryList) { + for (StaffAccountSummaryCollectionData.LoanAccountSummary clientSummary : clientSummaryList) { - final Collection clientLoanAccounts = this.clientReadPlatformService + final Collection clientLoanAccounts = this.accountDetailsReadPlatformService .retrieveClientLoanAccountsByLoanOfficerId(clientSummary.getId(), loanOfficerId); clientSummary.setLoans(clientLoanAccounts); } - final List groupSummaryList = this.jdbcTemplate.query(groupSql, staffGroupMapper, - new Object[] { loanOfficerId }); + final List groupSummaryList = this.jdbcTemplate.query(groupSql, staffGroupMapper, + new Object[] { loanOfficerId, GroupingTypeStatus.ACTIVE.getValue()}); - for (StaffAccountSummaryCollectionData.GroupSummary groupSummary : groupSummaryList) { + for (StaffAccountSummaryCollectionData.LoanAccountSummary groupSummary : groupSummaryList) { - final Collection groupLoanAccounts = this.groupReadPlatformService + final Collection groupLoanAccounts = this.accountDetailsReadPlatformService .retrieveGroupLoanAccountsByLoanOfficerId(groupSummary.getId(), loanOfficerId); groupSummary.setLoans(groupLoanAccounts); @@ -76,7 +74,7 @@ public class BulkLoansReadPlatformServiceImpl implements BulkLoansReadPlatformSe return new StaffAccountSummaryCollectionData(clientSummaryList, groupSummaryList); } - private static final class StaffClientMapper implements RowMapper { + private static final class StaffClientMapper implements RowMapper { public String schema() { return " c.id as id, c.display_name as displayName from m_client c " @@ -84,16 +82,16 @@ public class BulkLoansReadPlatformServiceImpl implements BulkLoansReadPlatformSe } @Override - public StaffAccountSummaryCollectionData.ClientSummary mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) + public StaffAccountSummaryCollectionData.LoanAccountSummary mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { final Long id = JdbcSupport.getLong(rs, "id"); final String displayName = rs.getString("displayName"); - return new StaffAccountSummaryCollectionData.ClientSummary(id, displayName); + return new StaffAccountSummaryCollectionData.LoanAccountSummary(id, displayName); } } - private static final class StaffGroupMapper implements RowMapper { + private static final class StaffGroupMapper implements RowMapper { public String schema() { return " g.id as id, g.display_name as name from m_group g" @@ -101,12 +99,12 @@ public class BulkLoansReadPlatformServiceImpl implements BulkLoansReadPlatformSe } @Override - public StaffAccountSummaryCollectionData.GroupSummary mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) + public StaffAccountSummaryCollectionData.LoanAccountSummary mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { final Long id = JdbcSupport.getLong(rs, "id"); final String name = rs.getString("name"); - return new StaffAccountSummaryCollectionData.GroupSummary(id, name); + return new StaffAccountSummaryCollectionData.LoanAccountSummary(id, name); } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index e11152d3b..c34b95a7b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -146,6 +146,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Override public CommandProcessingResult submitApplication(final JsonCommand command) { + try { final AppUser currentUser = context.authenticatedUser(); this.fromApiJsonDeserializer.validateForCreate(command.json()); @@ -207,8 +208,12 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa .withGroupId(newLoanApplication.getGroupId()) // .withLoanId(newLoanApplication.getId()) // .build(); + } catch (DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } } - + @Transactional @Override public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) { @@ -396,8 +401,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa logAsErrorUnexpectedDataIntegrityException(dve); throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); - } - + } + private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) { logger.error(dve.getMessage(), dve); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index e3e3d0aed..07cd579d8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -24,6 +24,7 @@ import org.mifosplatform.organisation.staff.exception.StaffNotFoundException; import org.mifosplatform.organisation.staff.exception.StaffRoleException; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.accountdetails.service.AccountEnumerations; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; @@ -42,6 +43,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository; @@ -55,7 +57,6 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; -import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -65,6 +66,8 @@ import com.google.gson.JsonElement; @Service public class LoanAssembler { + private final FromJsonHelper fromApiJsonHelper; + private final LoanRepositoryWrapper loanRepository; private final LoanProductRepository loanProductRepository; private final ClientRepositoryWrapper clientRepository; private final GroupRepository groupRepository; @@ -75,7 +78,6 @@ public class LoanAssembler { private final LoanScheduleAssembler loanScheduleAssembler; private final LoanChargeAssembler loanChargeAssembler; private final CollateralAssembler loanCollateralAssembler; - private final FromJsonHelper fromApiJsonHelper; private final LoanSummaryWrapper loanSummaryWrapper; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final HolidayRepository holidayRepository; @@ -83,8 +85,9 @@ public class LoanAssembler { private final WorkingDaysRepositoryWrapper workingDaysRepository; @Autowired - public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository, - final ClientRepositoryWrapper clientRepository, final GroupRepository groupRepository, final FundRepository fundRepository, + public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanRepositoryWrapper loanRepository, + final LoanProductRepository loanProductRepository, final ClientRepositoryWrapper clientRepository, + final GroupRepository groupRepository, final FundRepository fundRepository, final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository, final StaffRepository staffRepository, final CodeValueRepositoryWrapper codeValueRepository, final LoanScheduleAssembler loanScheduleAssembler, final LoanChargeAssembler loanChargeAssembler, @@ -93,6 +96,7 @@ public class LoanAssembler { final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, final WorkingDaysRepositoryWrapper workingDaysRepository) { this.fromApiJsonHelper = fromApiJsonHelper; + this.loanRepository = loanRepository; this.loanProductRepository = loanProductRepository; this.clientRepository = clientRepository; this.groupRepository = groupRepository; @@ -110,24 +114,32 @@ public class LoanAssembler { this.workingDaysRepository = workingDaysRepository; } + public Loan assembleFrom(final Long accountId) { + final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId); + loanAccount.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, + this.loanRepaymentScheduleTransactionProcessorFactory); + + return loanAccount; + } + public Loan assembleFrom(final JsonCommand command, final AppUser currentUser) { final JsonElement element = command.parsedJson(); - final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element); - final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element); + final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element); + final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element); return assembleApplication(element, clientId, groupId, currentUser); } private Loan assembleApplication(final JsonElement element, final Long clientId, final Long groupId, final AppUser currentUser) { - final String accountNo = fromApiJsonHelper.extractStringNamed("accountNo", element); - final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); - final Long fundId = fromApiJsonHelper.extractLongNamed("fundId", element); - final Long loanOfficerId = fromApiJsonHelper.extractLongNamed("loanOfficerId", element); - final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element); - final Long loanPurposeId = fromApiJsonHelper.extractLongNamed("loanPurposeId", element); - final Boolean syncDisbursementWithMeeting = fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element); + final String accountNo = this.fromApiJsonHelper.extractStringNamed("accountNo", element); + final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element); + final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element); + final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed("loanOfficerId", element); + final Long transactionProcessingStrategyId = this.fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element); + final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed("loanPurposeId", element); + final Boolean syncDisbursementWithMeeting = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element); final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } @@ -137,11 +149,11 @@ public class LoanAssembler { final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(transactionProcessingStrategyId); CodeValue loanPurpose = null; if (loanPurposeId != null) { - loanPurpose = codeValueRepository.findOneWithNotFoundDetection(loanPurposeId); + loanPurpose = this.codeValueRepository.findOneWithNotFoundDetection(loanPurposeId); } final Set collateral = this.loanCollateralAssembler.fromParsedJson(element); final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element); - for (LoanCharge loanCharge : loanCharges) { + for (final LoanCharge loanCharge : loanCharges) { if (!loanProduct.hasCurrencyCodeOf(loanCharge.currencyCode())) { final String errorMessage = "Charge and Loan must have the same currency."; throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage); @@ -155,14 +167,12 @@ public class LoanAssembler { final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element); final String loanTypeParameterName = "loanType"; - final String loanTypeStr = fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element); - final EnumOptionData loanType = LoanEnumerations.loanType(loanTypeStr); + final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element); + final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr); if (clientId != null) { client = this.clientRepository.findOneWithNotFoundDetection(clientId); - if (client.isNotActive()) { - throw new ClientNotActiveException(clientId); - } + if (client.isNotActive()) { throw new ClientNotActiveException(clientId); } loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral); @@ -171,9 +181,7 @@ public class LoanAssembler { if (groupId != null) { group = this.groupRepository.findOne(groupId); if (group == null) { throw new GroupNotFoundException(groupId); } - if (group.isNotActive()) { - throw new GroupNotActiveException(groupId); - } + if (group.isNotActive()) { throw new GroupNotActiveException(groupId); } loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting); @@ -184,11 +192,12 @@ public class LoanAssembler { if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); } loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(), - loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting); + loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, + syncDisbursementWithMeeting); } - final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element); - final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element); + final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element); + final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element); if (loanApplication == null) { throw new IllegalStateException("No loan application exists for either a client or group (or both)."); } loanApplication.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, @@ -196,11 +205,13 @@ public class LoanAssembler { final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(), loanApplicationTerms.getExpectedDisbursementDate().toDate()); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(), + loanApplicationTerms.getExpectedDisbursementDate().toDate()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); - final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element); + final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, + isHolidayEnabled, holidays, workingDays, element); loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(), submittedOnDate, externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay); @@ -208,7 +219,7 @@ public class LoanAssembler { } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { - List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); + final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); } @@ -256,7 +267,7 @@ public class LoanAssembler { loanApplication.getExpectedDisbursedOnLocalDate().toDate()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); - + loanApplication.validateExpectedDisbursementForHolidayAndNonWorkingDay(workingDays, allowTransactionsOnHoliday, holidays, allowTransactionsOnNonWorkingDay); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java index e2ddbd66d..9307315be 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java @@ -44,14 +44,19 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform private static final class LoanChargeMapper implements RowMapper { public String schema() { - return "lc.id as id, c.id as chargeId, c.name as name, " + "lc.amount as amountDue, " - + "lc.amount_paid_derived as amountPaid, " + "lc.amount_waived_derived as amountWaived, " - + "lc.amount_writtenoff_derived as amountWrittenOff, " + "lc.amount_outstanding_derived as amountOutstanding, " + return "lc.id as id, c.id as chargeId, c.name as name, " + + "lc.amount as amountDue, " + + "lc.amount_paid_derived as amountPaid, " + + "lc.amount_waived_derived as amountWaived, " + + "lc.amount_writtenoff_derived as amountWrittenOff, " + + "lc.amount_outstanding_derived as amountOutstanding, " + "lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, " - + "lc.charge_time_enum as chargeTime, " + "lc.is_penalty as penalty, " - + "lc.due_for_collection_as_of_date as dueAsOfDate, " + "lc.charge_calculation_enum as chargeCalculation, " + + "lc.charge_time_enum as chargeTime, " + + "lc.is_penalty as penalty, " + + "lc.due_for_collection_as_of_date as dueAsOfDate, " + + "lc.charge_calculation_enum as chargeCalculation, " + "c.currency_code as currencyCode, oc.name as currencyName, " - + "oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " + + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " + "oc.internationalized_name_code as currencyNameCode from m_charge c " + "join m_organisation_currency oc on c.currency_code = oc.code " + "join m_loan_charge lc on lc.charge_id = c.id "; } @@ -76,9 +81,10 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, currencyDisplaySymbol, - currencyNameCode); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); final int chargeTime = rs.getInt("chargeTime"); final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 71301f921..bc95be622 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -29,6 +29,7 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.accountdetails.service.AccountEnumerations; import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; @@ -40,6 +41,7 @@ import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; +import org.mifosplatform.portfolio.group.data.GroupRoleData; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; import org.mifosplatform.portfolio.group.service.SearchParameters; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; @@ -279,7 +281,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { // get group associations Collection membersOfGroup = this.clientReadPlatformService.retrieveClientMembersOfGroup(groupId); if (!CollectionUtils.isEmpty(membersOfGroup)) { - groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, null); + final Collection calendarsData = null; + final CalendarData collectionMeetingCalendar = null; + final Collection groupRoles = null; + groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, groupRoles, calendarsData, collectionMeetingCalendar); } final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant(); @@ -315,7 +320,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Collection paymentOptions = codeValueReadPlatformService .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); return new LoanTransactionData(null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, - possibleNextRepaymentAmount.getAmount(), null, null, null, null, paymentOptions); + possibleNextRepaymentAmount.getAmount(), null, null, null, null, paymentOptions, null); } @Override @@ -339,7 +344,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final BigDecimal amount = waiveOfInterest.getAmount(currency).getAmount(); return new LoanTransactionData(null, transactionType, null, currencyData, waiveOfInterest.getTransactionDate(), amount, null, null, - null, null); + null, null, null); } @Override @@ -348,7 +353,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { context.authenticatedUser(); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF); - return new LoanTransactionData(null, transactionType, null, null, DateUtils.getLocalDateOfTenant(), null, null, null, null, null); + return new LoanTransactionData(null, transactionType, null, null, DateUtils.getLocalDateOfTenant(), null, null, null, null, null, + null); } @Override @@ -359,7 +365,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Collection paymentOptions = codeValueReadPlatformService .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); return new LoanTransactionData(null, transactionType, null, null, loan.getExpectedDisbursedOnLocalDate(), null, null, null, null, - null, paymentOptions); + null, paymentOptions, null); } @Override @@ -408,7 +414,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " l.amortization_method_enum as amortizationType, l.interest_method_enum as interestType, l.interest_calculated_in_period_enum as interestCalculationPeriodType," + " l.loan_status_id as lifeCycleStatusId, l.loan_transaction_strategy_id as transactionStrategyId, " + " lps.name as transactionStrategyName, " - + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, " + + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, " + " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName, " + " l.principal_disbursed_derived as principalDisbursed," + " l.principal_repaid_derived as principalPaid," @@ -472,8 +478,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); final Long id = rs.getLong("id"); final String accountNo = rs.getString("accountNo"); @@ -492,7 +499,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final String groupHierarchy = rs.getString("groupHierarchy"); final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType"); - final EnumOptionData loanType = LoanEnumerations.loanType(loanTypeId); + final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId); final Long fundId = JdbcSupport.getLong(rs, "fundId"); final String fundName = rs.getString("fundName"); @@ -592,7 +599,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate"); final Boolean syncDisbursementWithMeeting = rs.getBoolean("syncDisbursementWithMeeting"); - + final BigDecimal feeChargesDueAtDisbursementCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesDueAtDisbursementCharged"); LoanSummaryData loanSummary = null; @@ -654,18 +661,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { groupData = GroupGeneralData.instance(groupId, groupName, groupExternalId, groupStatus, activationDate, groupOfficeId, null, groupParentId, null, groupStaffId, null, groupHierarchy); } - + final Integer loanCounter = JdbcSupport.getInteger(rs, "loanCounter"); final Integer loanProductCounter = JdbcSupport.getInteger(rs, "loanProductCounter"); return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, principal, totalOverpaid, inArrearsTolerance, termFrequency, - termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId, transactionStrategyName , - amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, - interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, - graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, - syncDisbursementWithMeeting, loanCounter, loanProductCounter); + termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId, + transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, + interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, + graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, + feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter); } } @@ -706,7 +713,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Collection periods = new ArrayList(); periods.add(disbursementPeriod); - final MonetaryCurrency monCurrency = new MonetaryCurrency(this.currency.code(), this.currency.decimalPlaces()); + final MonetaryCurrency monCurrency = new MonetaryCurrency(this.currency.code(), this.currency.decimalPlaces(), + this.currency.currencyInMultiplesOf()); final BigDecimal totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount(); Money totalPrincipalExpected = Money.zero(monCurrency); Money totalPrincipalPaid = Money.zero(monCurrency); @@ -842,9 +850,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, " + " pd.payment_type_cv_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, " + " pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, " - + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, " + + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, " + " rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, " - + " cv.code_value as paymentTypeName " + " from m_loan l join m_loan_transaction tr on tr.loan_id = l.id" + + " cv.code_value as paymentTypeName, tr.external_id as externalId " + + " from m_loan l join m_loan_transaction tr on tr.loan_id = l.id" + " join m_currency rc on rc.`code` = l.currency_code " + " left JOIN m_payment_detail pd ON tr.payment_detail_id = pd.id" + " left join m_code_value cv on pd.payment_type_cv_id = cv.id"; @@ -858,8 +867,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); final Long id = rs.getLong("id"); final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType"); @@ -887,9 +897,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final BigDecimal interestPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interest"); final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees"); final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties"); + final String externalId = rs.getString("externalId"); return new LoanTransactionData(id, transactionType, paymentDetailData, currencyData, date, totalAmount, principalPortion, - interestPortion, feeChargesPortion, penaltyChargesPortion); + interestPortion, feeChargesPortion, penaltyChargesPortion, externalId); } } @@ -951,7 +962,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { // get group associations Collection membersOfGroup = this.clientReadPlatformService.retrieveActiveClientMembersOfGroup(groupId); if (!CollectionUtils.isEmpty(membersOfGroup)) { - groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, null); + final Collection calendarsData = null; + final CalendarData collectionMeetingCalendar = null; + final Collection groupRoles = null; + groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, groupRoles, calendarsData, collectionMeetingCalendar); } return LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 193cde4c2..f84d63c7f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -34,11 +34,12 @@ import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; -import org.mifosplatform.portfolio.calendar.service.CalendarHelper; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException; @@ -60,18 +61,15 @@ import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; -import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; -import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; -import org.mifosplatform.portfolio.loanaccount.domain.LoanType; import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException; -import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException; @@ -99,6 +97,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final LoanEventApiJsonValidator loanEventApiJsonValidator; private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer; private final LoanRepository loanRepository; + private final LoanAccountDomainService loanAccountDomainService; private final NoteRepository noteRepository; private final LoanTransactionRepository loanTransactionRepository; private final LoanAssembler loanAssembler; @@ -106,8 +105,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final LoanChargeRepository loanChargeRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final JournalEntryWritePlatformService journalEntryWritePlatformService; - private final LoanSummaryWrapper loanSummaryWrapper; - private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanScheduleGeneratorFactory loanScheduleFactory; private final CalendarInstanceRepository calendarInstanceRepository; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; @@ -120,20 +117,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanEventApiJsonValidator loanEventApiJsonValidator, final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler, - final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, - final NoteRepository noteRepository, final ChargeRepositoryWrapper chargeRepository, - final LoanChargeRepository loanChargeRepository, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, - final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanSummaryWrapper loanSummaryWrapper, - final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + final LoanRepository loanRepository, final LoanAccountDomainService loanAccountDomainService, + final LoanTransactionRepository loanTransactionRepository, final NoteRepository noteRepository, + final ChargeRepositoryWrapper chargeRepository, final LoanChargeRepository loanChargeRepository, + final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, + final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository, final PaymentDetailWritePlatformService paymentDetailWritePlatformService, final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanProductReadPlatformService loanProductReadPlatformService) { - this.context = context; this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanAssembler = loanAssembler; this.loanRepository = loanRepository; + this.loanAccountDomainService = loanAccountDomainService; this.loanTransactionRepository = loanTransactionRepository; this.noteRepository = noteRepository; this.chargeRepository = chargeRepository; @@ -141,8 +138,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.applicationCurrencyRepository = applicationCurrencyRepository; this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.loanUpdateCommandFromApiJsonDeserializer = loanUpdateCommandFromApiJsonDeserializer; - this.loanSummaryWrapper = loanSummaryWrapper; - this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; this.loanScheduleFactory = loanScheduleFactory; this.calendarInstanceRepository = calendarInstanceRepository; this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; @@ -153,7 +148,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { - List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); + final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); } @@ -161,14 +156,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand command) { - final AppUser currentUser = context.authenticatedUser(); + final AppUser currentUser = this.context.authenticatedUser(); this.loanEventApiJsonValidator.validateDisbursement(command.json()); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); - //check for product mix validations + // check for product mix validations checkForProductMixRestrictions(loan); // validate actual disbursement date against meeting date @@ -188,13 +183,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Map changes = new LinkedHashMap(); - PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); // Recalculate first repayment date based in actual disbursement date. final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); - final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(actualDisbursementDate, loan, calendarInstance); + final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(actualDisbursementDate, loan, + calendarInstance); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), actualDisbursementDate.toDate()); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), + actualDisbursementDate.toDate()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); @@ -209,7 +206,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); + final Note note = Note.loanNote(loan, noteText); this.noteRepository.save(note); } @@ -229,7 +226,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, final CalendarInstance calendarInstance) { - final Calendar calendar = (calendarInstance == null) ? null : calendarInstance.getCalendar(); + final Calendar calendar = calendarInstance == null ? null : calendarInstance.getCalendar(); LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate(); if (calendar != null) {// sync repayments @@ -244,9 +241,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (repaymentScheduleDetails != null) {// Not expecting to be // null final Integer repayEvery = repaymentScheduleDetails.getRepayEvery(); - final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails + final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails .getRepaymentPeriodFrequencyType()); - calculatedRepaymentsStartingFromDate = CalendarHelper.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate, repayEvery, + calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate, repayEvery, frequency); } } @@ -260,16 +257,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public Map bulkLoanDisbursal(final JsonCommand command, final CollectionSheetBulkDisbursalCommand bulkDisbursalCommand) { - final AppUser currentUser = context.authenticatedUser(); + final AppUser currentUser = this.context.authenticatedUser(); - SingleDisbursalCommand[] disbursalCommand = bulkDisbursalCommand.getDisburseTransactions(); - Map changes = new LinkedHashMap(); + final SingleDisbursalCommand[] disbursalCommand = bulkDisbursalCommand.getDisburseTransactions(); + final Map changes = new LinkedHashMap(); if (disbursalCommand == null) { return changes; } for (int i = 0; i < disbursalCommand.length; i++) { - SingleDisbursalCommand singleLoanDisbursalCommand = disbursalCommand[i]; + final SingleDisbursalCommand singleLoanDisbursalCommand = disbursalCommand[i]; - final Loan loan = retrieveLoanBy(singleLoanDisbursalCommand.getLoanId()); + final Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId()); checkClientOrGroupActive(loan); final MonetaryCurrency currency = loan.getCurrency(); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -277,7 +274,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); - PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); // Bulk disbursement should happen on meeting date (mostly from // collection sheet). @@ -288,7 +285,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LocalDate firstRepaymentOnDate = null; final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), actualDisbursementDate.toDate()); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), + actualDisbursementDate.toDate()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); @@ -303,7 +301,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); + final Note note = Note.loanNote(loan, noteText); this.noteRepository.save(note); } @@ -317,9 +315,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult undoLoanDisbursal(final Long loanId, final JsonCommand command) { - context.authenticatedUser(); - - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); removeLoanCycle(loan); final List existingTransactionIds = new ArrayList(); @@ -330,7 +326,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); + final Note note = Note.loanNote(loan, noteText); this.noteRepository.save(note); } @@ -339,7 +335,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Map accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(), existingTransactionIds, existingReversedTransactionIds); - journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); } return new CommandProcessingResultBuilder() // @@ -357,12 +353,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult makeLoanRepayment(final Long loanId, final JsonCommand command) { - context.authenticatedUser(); + this.context.authenticatedUser(); this.loanEventApiJsonValidator.validateNewRepaymentTransaction(command.json()); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final Map changes = new LinkedHashMap(); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); @@ -375,10 +372,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changes.put("note", noteText); } - PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); - CommandProcessingResultBuilder commandProcessingResultBuilder = saveLoanRepayment(loanId, paymentDetail, transactionAmount, - transactionDate, noteText); + final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); + this.loanAccountDomainService.makeRepayment(loanId, commandProcessingResultBuilder, transactionDate, transactionAmount, + paymentDetail, noteText, txnExternalId); return commandProcessingResultBuilder.withCommandId(command.commandId()) // .withLoanId(loanId) // @@ -386,86 +384,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } - private CommandProcessingResultBuilder saveLoanRepayment(final Long loanId, final PaymentDetail paymentDetail, - final BigDecimal transactionAmount, final LocalDate transactionDate, final String noteText) { - final Loan loan = retrieveLoanBy(loanId); - checkClientOrGroupActive(loan); - - // TODO: Is it required to validate transaction date with meeting dates - // if repayments is synced with meeting? - /* - * if(loan.isSyncDisbursementWithMeeting()){ // validate actual - * disbursement date against meeting date CalendarInstance - * calendarInstance = - * this.calendarInstanceRepository.findCalendarInstaneByLoanId - * (loan.getId(), CalendarEntityType.LOANS.getValue()); - * this.loanEventApiJsonValidator - * .validateRepaymentDateWithMeetingDate(transactionDate, - * calendarInstance); } - */ - - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); - - final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount); - final LoanTransaction newRepaymentTransaction = LoanTransaction.repayment(repaymentAmount, paymentDetail, transactionDate); - final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), transactionDate.toDate()); - final WorkingDays workingDays = this.workingDaysRepository.findOne(); - final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); - - final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, - defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday, - holidays, workingDays, allowTransactionsOnNonWorkingDay); - - this.loanTransactionRepository.save(newRepaymentTransaction); - - /*** - * TODO Vishwas Batch save is giving me a - * HibernateOptimisticLockingFailureException, looping and saving for - * the time being, not a major issue for now as this loop is entered - * only in edge cases (when a payment is made before the latest payment - * recorded against the loan) - ***/ - if (changedTransactionDetail != null) { - for (LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { - this.loanTransactionRepository.save(loanTransaction); - } - } - this.loanRepository.save(loan); - - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText); - this.noteRepository.save(note); - } - - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - - return new CommandProcessingResultBuilder() // - .withEntityId(newRepaymentTransaction.getId()) // - .withOfficeId(loan.getOfficeId()) // - .withClientId(loan.getClientId()) // - .withGroupId(loan.getGroupId()); // - } - @Transactional @Override public Map makeLoanBulkRepayment(final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand) { - context.authenticatedUser(); + this.context.authenticatedUser(); final SingleRepaymentCommand[] repaymentCommand = bulkRepaymentCommand.getLoanTransactions(); final Map changes = new LinkedHashMap(); - if (repaymentCommand == null) return changes; + if (repaymentCommand == null) { return changes; } - for (SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) { + for (final SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) { /**** * TODO Vishwas, have a re-look at this implementation, defaulting * it to null for now ***/ - PaymentDetail paymentDetail = null; - saveLoanRepayment(singleLoanRepaymentCommand.getLoanId(), paymentDetail, singleLoanRepaymentCommand.getTransactionAmount(), - bulkRepaymentCommand.getTransactionDate(), bulkRepaymentCommand.getNote()); + final PaymentDetail paymentDetail = null; + final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); + this.loanAccountDomainService.makeRepayment(singleLoanRepaymentCommand.getLoanId(), commandProcessingResultBuilder, + bulkRepaymentCommand.getTransactionDate(), singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, + bulkRepaymentCommand.getNote(), null); + changes.put("bulkTransations", singleLoanRepaymentCommand); } return changes; @@ -475,11 +414,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) { - context.authenticatedUser(); + this.context.authenticatedUser(); this.loanEventApiJsonValidator.validateTransaction(command.json()); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(transactionId); @@ -487,6 +426,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final Map changes = new LinkedHashMap(); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); @@ -498,14 +438,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingReversedTransactionIds = new ArrayList(); final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount); - PaymentDetail paymentDetail = paymentDetailWritePlatformService.createPaymentDetail(command, changes); - LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmountAsMoney, paymentDetail, transactionDate); + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createPaymentDetail(command, changes); + LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmountAsMoney, paymentDetail, transactionDate, + txnExternalId); if (transactionToAdjust.isInterestWaiver()) { newTransactionDetail = LoanTransaction.waiver(loan, transactionAmountAsMoney, transactionDate); } final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), transactionDate.toDate()); + final List holidays = this.holidayRepository + .findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), transactionDate.toDate()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); @@ -515,7 +457,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { if (paymentDetail != null) { - paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail); + this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail); } this.loanTransactionRepository.save(newTransactionDetail); } @@ -528,7 +470,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf * payment recorded against the loan) ***/ if (changedTransactionDetail != null) { - for (LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { + for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { this.loanTransactionRepository.save(loanTransaction); } } @@ -568,7 +510,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final JsonCommand command) { - context.authenticatedUser(); + this.context.authenticatedUser(); this.loanEventApiJsonValidator.validateTransaction(command.json()); @@ -580,14 +522,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount); - LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan, transactionAmountAsMoney, transactionDate); + final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan, transactionAmountAsMoney, transactionDate); final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds); @@ -602,7 +544,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf * recorded against the loan) ***/ if (changedTransactionDetail != null) { - for (LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { + for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { this.loanTransactionRepository.save(loanTransaction); } } @@ -641,7 +583,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changes.put("locale", command.locale()); changes.put("dateFormat", command.dateFormat()); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); removeLoanCycle(loan); @@ -677,11 +619,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand command) { - context.authenticatedUser(); + this.context.authenticatedUser(); this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json()); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final Map changes = new LinkedHashMap(); @@ -740,11 +682,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public CommandProcessingResult closeAsRescheduled(final Long loanId, final JsonCommand command) { - context.authenticatedUser(); + this.context.authenticatedUser(); this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json()); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); removeLoanCycle(loan); @@ -783,7 +725,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.loanEventApiJsonValidator.validateAddLoanCharge(command.json()); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId"); @@ -816,7 +758,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf * latest payment recorded against the loan) ***/ if (changedTransactionDetail != null) { - for (LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { + for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { this.loanTransactionRepository.save(loanTransaction); } } @@ -844,7 +786,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.loanEventApiJsonValidator.validateUpdateOfLoanCharge(command.json()); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); @@ -874,7 +816,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.context.authenticatedUser(); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); @@ -919,7 +861,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.context.authenticatedUser(); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); @@ -941,13 +883,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } - private Loan retrieveLoanBy(final Long loanId) { - final Loan loan = this.loanRepository.findOne(loanId); - if (loan == null) { throw new LoanNotFoundException(loanId); } - loan.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory); - return loan; - } - private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) { final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId); if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); } @@ -967,11 +902,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Long fromLoanOfficerId = command.longValueOfParameterNamed("fromLoanOfficerId"); final Long toLoanOfficerId = command.longValueOfParameterNamed("toLoanOfficerId"); - final Staff fromLoanOfficer = loanAssembler.findLoanOfficerByIdIfProvided(fromLoanOfficerId); - final Staff toLoanOfficer = loanAssembler.findLoanOfficerByIdIfProvided(toLoanOfficerId); + final Staff fromLoanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(fromLoanOfficerId); + final Staff toLoanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(toLoanOfficerId); final LocalDate dateOfLoanOfficerAssignment = command.localDateValueOfParameterNamed("assignmentDate"); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); } @@ -995,6 +930,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf public CommandProcessingResult bulkLoanReassignment(final JsonCommand command) { this.context.authenticatedUser(); + this.loanEventApiJsonValidator.validateForBulkLoanReassignment(command.json()); final Long fromLoanOfficerId = command.longValueOfParameterNamed("fromLoanOfficerId"); final Long toLoanOfficerId = command.longValueOfParameterNamed("toLoanOfficerId"); @@ -1002,12 +938,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LocalDate dateOfLoanOfficerAssignment = command.localDateValueOfParameterNamed("assignmentDate"); - final Staff fromLoanOfficer = loanAssembler.findLoanOfficerByIdIfProvided(fromLoanOfficerId); - final Staff toLoanOfficer = loanAssembler.findLoanOfficerByIdIfProvided(toLoanOfficerId); + final Staff fromLoanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(fromLoanOfficerId); + final Staff toLoanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(toLoanOfficerId); for (final String loanIdString : loanIds) { final Long loanId = Long.valueOf(loanIdString); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); } @@ -1015,7 +951,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment); this.loanRepository.save(loan); } - this.loanRepository.flush(); return new CommandProcessingResultBuilder() // @@ -1035,7 +970,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LocalDate dateOfLoanOfficerunAssigned = command.localDateValueOfParameterNamed("unassignedDate"); - final Loan loan = retrieveLoanBy(loanId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); if (loan.getLoanOfficer() == null) { throw new LoanOfficerUnassignmentException(loanId); } @@ -1062,31 +997,33 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Map accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(), existingTransactionIds, existingReversedTransactionIds); - journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); } @Transactional @Override - public void applyMeetingDateChanges(final Calendar calendar, Collection loanCalendarInstances) { - + public void applyMeetingDateChanges(final Calendar calendar, final Collection loanCalendarInstances) { + final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final Collection loanStatuses = new ArrayList(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue())); - final Collection loanTypes = new ArrayList(Arrays.asList(LoanType.GROUP.getValue(), LoanType.JLG.getValue())); + final Collection loanTypes = new ArrayList( + Arrays.asList(AccountType.GROUP.getValue(), AccountType.JLG.getValue())); final Collection loanIds = new ArrayList(loanCalendarInstances.size()); // loop through loanCalendarInstances to get loan ids - for (CalendarInstance calendarInstance : loanCalendarInstances) { + for (final CalendarInstance calendarInstance : loanCalendarInstances) { loanIds.add(calendarInstance.getEntityId()); } final List loans = this.loanRepository.findByIdsAndLoanStatusAndLoanType(loanIds, loanStatuses, loanTypes); List holidays = null; // loop through each loan to reschedule the repayment dates - for (Loan loan : loans) { + for (final Loan loan : loans) { if (loan != null) { holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); - loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, holidays, workingDays); + loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, + holidays, workingDays); this.loanRepository.save(loan); } } @@ -1097,10 +1034,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (loan.isGroupLoan()) { if (loan.loanProduct().isIncludeInBorrowerCycle()) { loansToUpdate = this.loanRepository.getGroupLoansToUpdateLoanCounter(loan.getCurrentLoanCounter(), loan.getGroupId(), - LoanType.GROUP.getValue()); + AccountType.GROUP.getValue()); } else { loansToUpdate = this.loanRepository.getGroupLoansToUpdateLoanProductCounter(loan.getLoanProductLoanCounter(), - loan.getGroupId(), LoanType.GROUP.getValue()); + loan.getGroupId(), AccountType.GROUP.getValue()); } } else { @@ -1125,7 +1062,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (loan.isGroupLoan()) { final List loansToUpdateForLoanCounter = this.loanRepository.getGroupLoansDisbursedAfter(actualDisbursementDate.toDate(), - loan.getGroupId(), LoanType.GROUP.getValue()); + loan.getGroupId(), AccountType.GROUP.getValue()); final Integer newLoanCounter = getNewGroupLoanCounter(loan); final Integer newLoanProductCounter = getNewGroupLoanProductCounter(loan); updateLoanCounter(loan, loansToUpdateForLoanCounter, newLoanCounter, newLoanProductCounter); @@ -1140,7 +1077,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private Integer getNewGroupLoanCounter(final Loan loan) { - Integer maxClientLoanCounter = this.loanRepository.getMaxGroupLoanCounter(loan.getGroupId(), LoanType.GROUP.getValue()); + Integer maxClientLoanCounter = this.loanRepository.getMaxGroupLoanCounter(loan.getGroupId(), AccountType.GROUP.getValue()); if (maxClientLoanCounter == null) { maxClientLoanCounter = 1; } else { @@ -1152,7 +1089,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private Integer getNewGroupLoanProductCounter(final Loan loan) { Integer maxLoanProductLoanCounter = this.loanRepository.getMaxGroupLoanProductCounter(loan.loanProduct().getId(), - loan.getGroupId(), LoanType.GROUP.getValue()); + loan.getGroupId(), AccountType.GROUP.getValue()); if (maxLoanProductLoanCounter == null) { maxLoanProductLoanCounter = 1; } else { @@ -1245,32 +1182,33 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf public void applyHolidaysToLoans() { final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - - if(!isHolidayEnabled) return; - + + if (!isHolidayEnabled) { return; } + final Collection loanStatuses = new ArrayList(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue())); // Get all Holidays which are yet to applied to loans - List holidays = this.holidayRepository.findUnprocessed(); + final List holidays = this.holidayRepository.findUnprocessed(); // Loop through all holidays - for (Holiday holiday : holidays) { + for (final Holiday holiday : holidays) { // All offices to which holiday is applied final Set offices = holiday.getOffices(); - Collection officeIds = new ArrayList(offices.size()); - for (Office office : offices) { + final Collection officeIds = new ArrayList(offices.size()); + for (final Office office : offices) { officeIds.add(office.getId()); } // get all loans - List loans = new ArrayList(); + final List loans = new ArrayList(); // get all individual and jlg loans loans.addAll(this.loanRepository.findByClientOfficeIdsAndLoanStatus(officeIds, loanStatuses)); - //FIXME: AA optimize to get all client and group loans belongs to a office id - //get all group loans + // FIXME: AA optimize to get all client and group loans belongs to a + // office id + // get all group loans loans.addAll(this.loanRepository.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses)); - for (Loan loan : loans) { + for (final Loan loan : loans) { // apply holiday loan.applyHolidayToRepaymentScheduleDates(holiday); } @@ -1300,7 +1238,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (!CollectionUtils.isEmpty(activeLoansLoanProductIds)) { final Collection restrictedPrdouctsList = this.loanProductReadPlatformService .retrieveRestrictedProductsForMix(productId); - for (LoanProductData restrictedProduct : restrictedPrdouctsList) { + for (final LoanProductData restrictedProduct : restrictedPrdouctsList) { if (activeLoansLoanProductIds.contains(restrictedProduct.getId())) { throw new LoanDisbursalException(productName, restrictedProduct.getName()); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java index 6231b8454..167fc2c69 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -69,7 +69,7 @@ public class LoanProductsApiResource { "graceOnInterestPayment", "graceOnInterestCharged", "interestRatePerPeriod", "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "annualInterestRate", "amortizationType", "interestType", "interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "transactionProcessingStrategyName", - "charges", "accountingRule", "accountingMappings", "paymentChannelToFundSourceMappings", "fundOptions", "paymentTypeOptions", + "charges", "accountingRule", "externalId", "accountingMappings", "paymentChannelToFundSourceMappings", "fundOptions", "paymentTypeOptions", "currencyOptions", "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions", "accountingRuleOptions", "accountingMappingOptions")); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index c1d28c0a5..57380472a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -42,6 +42,7 @@ public class LoanProductData { private final LocalDate startDate; private final LocalDate closeDate; private final String status; + private final String externalId; // terms private final CurrencyData currency; private final BigDecimal principal; @@ -135,12 +136,13 @@ public class LoanProductData { final LocalDate startDate = null; final LocalDate closeDate = null; final String status = null; + final String externalId = null; return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, - accountingType, includeInBorrowerCycle, startDate, closeDate, status); + accountingType, includeInBorrowerCycle, startDate, closeDate, status, externalId); } public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) { @@ -176,13 +178,14 @@ public class LoanProductData { final LocalDate startDate = null; final LocalDate closeDate = null; final String status = null; + final String externalId = null; return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, - accountingType, includeInBorrowerCycle, startDate, closeDate, status); + accountingType, includeInBorrowerCycle, startDate, closeDate, status, externalId); } public static LoanProductData sensibleDefaultsForNewLoanProductCreation() { @@ -224,13 +227,14 @@ public class LoanProductData { final LocalDate startDate = null; final LocalDate closeDate = null; final String status = null; + final String externalId = null; return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, - accountingType, includeInBorrowerCycle, startDate, closeDate, status); + accountingType, includeInBorrowerCycle, startDate, closeDate, status, externalId); } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings, @@ -254,7 +258,7 @@ public class LoanProductData { final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final Collection charges, final EnumOptionData accountingType, final boolean includeInBorrowerCycle, - final LocalDate startDate, final LocalDate closeDate, final String status) { + final LocalDate startDate, final LocalDate closeDate, final String status, final String externalId) { this.id = id; this.name = name; this.description = description; @@ -289,6 +293,7 @@ public class LoanProductData { this.startDate = startDate; this.closeDate = closeDate; this.status = status; + this.externalId = externalId; this.chargeOptions = null; this.penaltyOptions = null; @@ -343,6 +348,7 @@ public class LoanProductData { this.startDate = productData.startDate; this.closeDate = productData.closeDate; this.status = productData.status; + this.externalId = productData.externalId; this.charges = nullIfEmpty(productData.charges()); this.accountingRule = productData.accountingRule; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 2d4164b1e..df45afe13 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -47,7 +47,8 @@ import com.google.gson.JsonArray; * They allow for constraints to be added at product level. */ @Entity -@Table(name = "m_product_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") }) +@Table(name = "m_product_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name"), +@UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") }) public class LoanProduct extends AbstractPersistable { @ManyToOne @@ -87,6 +88,9 @@ public class LoanProduct extends AbstractPersistable { @Column(name = "close_date", nullable = true) @Temporal(TemporalType.DATE) private Date closeDate; + + @Column(name = "external_id", length = 100, nullable = true, unique = true) + private String externalId; public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, final List productCharges, final JsonCommand command, final AprCalculator aprCalculator) { @@ -95,8 +99,9 @@ public class LoanProduct extends AbstractPersistable { final String description = command.stringValueOfParameterNamed("description"); final String currencyCode = command.stringValueOfParameterNamed("currencyCode"); final Integer digitsAfterDecimal = command.integerValueOfParameterNamed("digitsAfterDecimal"); - - final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal); + final Integer inMultiplesOf = command.integerValueOfParameterNamed("inMultiplesOf"); + + final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal,inMultiplesOf); final BigDecimal principal = command.bigDecimalValueOfParameterNamed("principal"); final BigDecimal minPrincipal = command.bigDecimalValueOfParameterNamed("minPrincipal"); final BigDecimal maxPrincipal = command.bigDecimalValueOfParameterNamed("maxPrincipal"); @@ -130,12 +135,13 @@ public class LoanProduct extends AbstractPersistable { final LocalDate startDate = command.localDateValueOfParameterNamed("startDate"); final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate"); + final String externalId = command.stringValueOfParameterNamedAllowingNull("externalId"); return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, - amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate); + amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate, externalId); } protected LoanProduct() { @@ -155,7 +161,7 @@ public class LoanProduct extends AbstractPersistable { final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List charges, final AccountingRuleType accountingRuleType, final boolean includeInBorrowerCycle, final LocalDate startDate, - final LocalDate closeDate) { + final LocalDate closeDate, final String externalId) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -192,6 +198,8 @@ public class LoanProduct extends AbstractPersistable { if (closeDate != null) { this.closeDate = closeDate.toDateMidnight().toDate(); } + + this.externalId = externalId; } public MonetaryCurrency getCurrency() { @@ -329,6 +337,14 @@ public class LoanProduct extends AbstractPersistable { this.closeDate = null; } } + + final String externalIdTypeParamName = "externalId"; + if (command.isChangeInStringParameterNamed(externalIdTypeParamName, this.externalId)) { + final String newValue = command.stringValueOfParameterNamed(externalIdTypeParamName); + actualChanges.put(accountingTypeParamName, newValue); + this.externalId = newValue; + } + return actualChanges; } @@ -412,5 +428,9 @@ public class LoanProduct extends AbstractPersistable { public String productName() { return this.name; } + + public String getExternalId() { + return externalId; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 2bfce6916..76d58ce9f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -206,6 +206,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche String currencyCode = this.currency.getCode(); Integer digitsAfterDecimal = this.currency.getDigitsAfterDecimal(); + Integer inMultiplesOf = this.currency.getCurrencyInMultiplesOf(); final String digitsAfterDecimalParamName = "digitsAfterDecimal"; if (command.isChangeInIntegerParameterNamed(digitsAfterDecimalParamName, digitsAfterDecimal)) { @@ -213,7 +214,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche actualChanges.put(digitsAfterDecimalParamName, newValue); actualChanges.put("locale", localeAsInput); digitsAfterDecimal = newValue; - this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal); + this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal,inMultiplesOf); } final String currencyCodeParamName = "currencyCode"; @@ -221,9 +222,17 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName); actualChanges.put(currencyCodeParamName, newValue); currencyCode = newValue; - this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal); + this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal,inMultiplesOf); } - + + final String inMultiplesOfParamName = "inMultiplesOf"; + if (command.isChangeInStringParameterNamed(inMultiplesOfParamName, currencyCode)) { + final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName); + actualChanges.put(inMultiplesOfParamName, newValue); + inMultiplesOf = newValue; + this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal,inMultiplesOf); + } + final Map loanApplicationAttributeChanges = updateLoanApplicationAttributes(command, aprCalculator); actualChanges.putAll(loanApplicationAttributeChanges); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java index d57fd8b16..f195673cb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -39,11 +39,11 @@ public final class LoanProductDataValidator { * The parameters supported for this command. */ private final Set supportedParameters = new HashSet(Arrays.asList("locale", "dateFormat", "name", "description", "fundId", - "currencyCode", "digitsAfterDecimal", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", "numberOfRepayments", + "currencyCode", "digitsAfterDecimal","inMultiplesOf", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", "numberOfRepayments", "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod", "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "graceOnPrincipalPayment", - "graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule","includeInBorrowerCycle", "startDate", "closeDate", + "graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule","includeInBorrowerCycle", "startDate", "closeDate", "externalId", LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), @@ -91,6 +91,9 @@ public final class LoanProductDataValidator { final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerNamed("digitsAfterDecimal", element, Locale.getDefault()); baseDataValidator.reset().parameter("digitsAfterDecimal").value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6); + + final Integer inMultiplesOf = fromApiJsonHelper.extractIntegerNamed("inMultiplesOf", element, Locale.getDefault()); + baseDataValidator.reset().parameter("inMultiplesOf").value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater(); final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount(); @@ -334,6 +337,11 @@ public final class LoanProductDataValidator { final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerNamed("digitsAfterDecimal", element, Locale.getDefault()); baseDataValidator.reset().parameter("digitsAfterDecimal").value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6); } + + if (fromApiJsonHelper.parameterExists("inMultiplesOf", element)) { + final Integer inMultiplesOf = fromApiJsonHelper.extractIntegerNamed("inMultiplesOf", element, Locale.getDefault()); + baseDataValidator.reset().parameter("inMultiplesOf").value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater(); + } String minPrincipalParameterName = "minPrincipal"; BigDecimal minPrincipalAmount = null; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index ee5ba9673..6ebdea8e9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -6,17 +6,17 @@ package org.mifosplatform.portfolio.loanproduct.service; import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.accountdetails.service.AccountEnumerations; import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; -import org.mifosplatform.portfolio.loanaccount.domain.LoanType; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; -public class LoanEnumerations { +public class LoanEnumerations{ public static final String LOAN_TERM_FREQUENCY_TYPE = "loanTermFrequencyType"; public static final String TERM_FREQUENCY_TYPE = "termFrequencyType"; @@ -44,7 +44,7 @@ public class LoanEnumerations { return interestType(id); } else if (typeName.equals(INTEREST_CALCULATION_PERIOD_TYPE)) { return interestCalculationPeriodType(id); - } else if (typeName.equals(LOAN_TYPE)) { return loanType(id); } + } else if (typeName.equals(LOAN_TYPE)) { return AccountEnumerations.loanType(id); } return null; } @@ -333,32 +333,4 @@ public class LoanEnumerations { return optionData; } - - public static EnumOptionData loanType(final Integer loanTypeId) { - return loanType(LoanType.fromInt(loanTypeId)); - } - - public static EnumOptionData loanType(final String name) { - return loanType(LoanType.fromName(name)); - } - - public static EnumOptionData loanType(final LoanType type) { - EnumOptionData optionData = new EnumOptionData(LoanType.INVALID.getValue().longValue(), LoanType.INVALID.getCode(), "Invalid"); - switch (type) { - case INVALID: - optionData = new EnumOptionData(LoanType.INVALID.getValue().longValue(), LoanType.INVALID.getCode(), "Invalid"); - break; - case INDIVIDUAL: - optionData = new EnumOptionData(LoanType.INDIVIDUAL.getValue().longValue(), LoanType.INDIVIDUAL.getCode(), "Individual"); - break; - case GROUP: - optionData = new EnumOptionData(LoanType.GROUP.getValue().longValue(), LoanType.GROUP.getCode(), "Group"); - break; - case JLG: - optionData = new EnumOptionData(LoanType.JLG.getValue().longValue(), LoanType.JLG.getCode(), "JLG"); - break; - } - - return optionData; - } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 5ba844d04..44ba1e579 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -99,14 +99,14 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo public String loanProductSchema() { return "lp.id as id, lp.fund_id as fundId, f.name as fundName, lp.loan_transaction_strategy_id as transactionStrategyId, ltps.name as transactionStrategyName, " + "lp.name as name, lp.description as description, " - + "lp.principal_amount as principal, lp.min_principal_amount as minPrincipal, lp.max_principal_amount as maxPrincipal, lp.currency_code as currencyCode, lp.currency_digits as currencyDigits, " + + "lp.principal_amount as principal, lp.min_principal_amount as minPrincipal, lp.max_principal_amount as maxPrincipal, lp.currency_code as currencyCode, lp.currency_digits as currencyDigits, lp.currency_multiplesof as inMultiplesOf, " + "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.min_nominal_interest_rate_per_period as minInterestRatePerPeriod, lp.max_nominal_interest_rate_per_period as maxInterestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, " + "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod," + "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, " + "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged," + "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, " + "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, " - + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol " + + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId " + " from m_product_loan lp " + " left join m_fund f on f.id = lp.fund_id" + " left join ref_loan_transaction_processing_strategy ltps on ltps.id = lp.loan_transaction_strategy_id" @@ -129,9 +129,10 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); final BigDecimal principal = rs.getBigDecimal("principal"); final BigDecimal minPrincipal = rs.getBigDecimal("minPrincipal"); @@ -170,7 +171,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final int interestCalculationPeriodTypeId = JdbcSupport.getInteger(rs, "interestCalculationInPeriodMethod"); final EnumOptionData interestCalculationPeriodType = LoanEnumerations .interestCalculationPeriodType(interestCalculationPeriodTypeId); - + final boolean includeInBorrowerCycle = rs.getBoolean("includeInBorrowerCycle"); final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate"); final LocalDate closeDate = JdbcSupport.getLocalDate(rs, "closeDate"); @@ -180,13 +181,15 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo } else { status = "loanProduct.active"; } - + final String externalId = rs.getString("externalId"); + return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, transactionStrategyId, transactionStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, - graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate, status); + graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate, status, + externalId); } } @@ -201,11 +204,11 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo return "lp.id as id, lp.name as name FROM m_product_loan lp left join m_product_mix pm on pm.product_id=lp.id where lp.id not IN(" + "select lp.id from m_product_loan lp inner join m_product_mix pm on pm.product_id=lp.id)"; } - + public String restrictedProductsSchema() { return "pm.restricted_product_id as id, rp.name as name from m_product_mix pm join m_product_loan rp on rp.id = pm.restricted_product_id "; } - + public String derivedRestrictedProductsSchema() { return "pm.product_id as id, lp.name as name from m_product_mix pm join m_product_loan lp on lp.id=pm.product_id"; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index a0709ed0e..197ab221b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -226,7 +226,13 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) { Throwable realCause = dve.getMostSpecificCause(); - if (realCause.getMessage().contains("unq_name")) { + + if (realCause.getMessage().contains("external_id")) { + + final String externalId = command.stringValueOfParameterNamed("externalId"); + throw new PlatformDataIntegrityException("error.msg.product.loan.duplicate.externalId", "Loan Product with externalId `" + externalId + + "` already exists", "externalId", externalId); + } else if (realCause.getMessage().contains("unq_name")) { final String name = command.stringValueOfParameterNamed("name"); throw new PlatformDataIntegrityException("error.msg.product.loan.duplicate.name", "Loan product with name `" + name diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/MeetingApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/MeetingApiConstants.java new file mode 100644 index 000000000..8e5850057 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/MeetingApiConstants.java @@ -0,0 +1,44 @@ +/** + * 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.meeting; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class MeetingApiConstants { + + public static final String MEETING_RESOURCE_NAME = "meeting"; + + // general + public static final String localeParamName = "locale"; + public static final String dateFormatParamName = "dateFormat"; + + // meetings parameters + public static final String idParamName = "id"; + public static final String meetingDateParamName = "meetingDate"; + public static final String calendarIdParamName = "calendarId"; + public static final String clientsAttendanceParamName = "clientsAttendance"; + + // attendance parameters + public static final String clientIdParamName = "clientId"; + public static final String attendanceTypeParamName = "attendanceType"; + + // attendance response parameters + public static final String clientsAttendance = "clientsAttendance"; + + // template response parameters + public static final String clients = "clients"; + public static final String calendarData = "calendarData"; + public static final String attendanceTypeOptions = "attendanceTypeOptions"; + + public static final Set MEETING_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(meetingDateParamName, + localeParamName, dateFormatParamName, calendarIdParamName, clientsAttendanceParamName)); + + public static final Set MEETING_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, meetingDateParamName, + clientsAttendance, clients, calendarData, attendanceTypeOptions)); + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java new file mode 100644 index 000000000..e69c70753 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java @@ -0,0 +1,221 @@ +/** + * 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.meeting.api; + +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.MEETING_RESOURCE_NAME; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.MEETING_RESPONSE_DATA_PARAMETERS; + +import java.util.Collection; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang.StringUtils; +import org.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.commands.service.CommandWrapperBuilder; +import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; +import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException; +import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.calendar.data.CalendarData; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; +import org.mifosplatform.portfolio.client.data.ClientData; +import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; +import org.mifosplatform.portfolio.meeting.attendance.data.ClientAttendanceData; +import org.mifosplatform.portfolio.meeting.attendance.service.AttendanceDropdownReadPlatformService; +import org.mifosplatform.portfolio.meeting.attendance.service.ClientAttendanceReadPlatformService; +import org.mifosplatform.portfolio.meeting.data.MeetingData; +import org.mifosplatform.portfolio.meeting.exception.MeetingNotSupportedResourceException; +import org.mifosplatform.portfolio.meeting.service.MeetingReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/{entityType}/{entityId}/meetings") +@Component +@Scope("singleton") +public class MeetingsApiResource { + + private final PlatformSecurityContext context; + private final MeetingReadPlatformService readPlatformService; + private final ClientAttendanceReadPlatformService attendanceReadPlatformService; + private final ClientReadPlatformService clientReadPlatformService; + private final CalendarReadPlatformService calendarReadPlatformService; + private final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + + @Autowired + public MeetingsApiResource(PlatformSecurityContext context, MeetingReadPlatformService readPlatformService, + ClientAttendanceReadPlatformService attendanceReadPlatformService, final ClientReadPlatformService clientReadPlatformService, + final CalendarReadPlatformService calendarReadPlatformService, + final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService, + DefaultToApiJsonSerializer toApiJsonSerializer, ApiRequestParameterHelper apiRequestParameterHelper, + PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + this.context = context; + this.readPlatformService = readPlatformService; + this.attendanceReadPlatformService = attendanceReadPlatformService; + this.clientReadPlatformService = clientReadPlatformService; + this.calendarReadPlatformService = calendarReadPlatformService; + this.attendanceDropdownReadPlatformService = attendanceDropdownReadPlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + } + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String template(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId, + @QueryParam("calendarId") Long calendarId, @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME); + final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(); + Collection clients = null; + CalendarData calendarData = null; + + if (CalendarEntityType.isGroup(entityType)) { + clients = this.clientReadPlatformService.retrieveActiveClientMembersOfGroup(entityId); + } else if (CalendarEntityType.isCenter(entityType)) { + clients = this.clientReadPlatformService.retrieveActiveClientMembersOfCenter(entityId); + } else { + final String defaultUserMessage = "Meeting attendance is not supported for the resource " + entityType + + ". The supported resources are [" + CalendarEntityType.GROUPS.name() + ", " + CalendarEntityType.CENTERS.name() + "]"; + throw new MeetingNotSupportedResourceException(defaultUserMessage, CalendarEntityType.GROUPS.name(), CalendarEntityType.CENTERS.name()); + } + + if (calendarId != null) { + calendarData = this.calendarReadPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId); + calendarData = this.calendarReadPlatformService.generateRecurringDate(calendarData, DateUtils.getLocalDateOfTenant()); + } + + final MeetingData meetingData = MeetingData.template(clients, calendarData, + attendanceDropdownReadPlatformService.retrieveAttendanceTypeOptions()); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, meetingData, MEETING_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveMeetings(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId, + @QueryParam("limit") final Integer limit, @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME); + + final Collection meetingsData = this.readPlatformService.retrieveMeetingsByEntity(entityId, CalendarEntityType + .valueOf(entityType.toUpperCase()).getValue(), limit); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, meetingsData, MEETING_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Path("{meetingId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveMeeting(@PathParam("meetingId") final Long meetingId, @PathParam("entityType") final String entityType, + @PathParam("entityId") final Long entityId, @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME); + final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(); + MeetingData meetingData = this.readPlatformService.retrieveMeeting(meetingId, entityId, entityTypeId); + final Collection clientsAttendance = this.attendanceReadPlatformService + .retrieveClientAttendanceByMeetingId(meetingId); + meetingData = MeetingData.withClientsAttendanceAndAttendanceTypeOptions(meetingData, clientsAttendance, + attendanceDropdownReadPlatformService.retrieveAttendanceTypeOptions()); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, meetingData, MEETING_RESPONSE_DATA_PARAMETERS); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String createMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId, + final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createMeeting(entityType, entityId) + .withJson(apiRequestBodyAsJson).build(); + + CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + + } + + @POST + @Path("{meetingId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String performMeetingCommands(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId, + @PathParam("meetingId") final Long meetingId, @QueryParam("command") final String commandParam, + final String apiRequestBodyAsJson) { + + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); + + CommandProcessingResult result = null; + if (is(commandParam, "saveOrUpdateAttendance")) { + final CommandWrapper commandRequest = builder.saveOrUpdateAttendance(meetingId, entityType, entityId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else { + throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "saveOrUpdateAttendance" }); + } + + return this.toApiJsonSerializer.serialize(result); + + } + + @PUT + @Path("{meetingId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String updateMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId, + @PathParam("meetingId") final Long meetingId, final String jsonRequestBody) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateMeeting(entityType, entityId, meetingId) + .withJson(jsonRequestBody).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @DELETE + @Path("{meetingId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String deleteMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId, + @PathParam("meetingId") final Long meetingId) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteMeeting(entityType, entityId, meetingId).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/AttendanceType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/AttendanceType.java new file mode 100644 index 000000000..92fc1383b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/AttendanceType.java @@ -0,0 +1,63 @@ +/** + * 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.meeting.attendance; + +import org.mifosplatform.portfolio.meeting.attendance.domain.ClientAttendance; + +/** + * An enumeration of {@link ClientAttendance} type. + */ +public enum AttendanceType { + + INVALID(0, "attendanceType.invalid"), // + PRESENT(1, "attendanceType.present"), // + ABSENT(2, "attendanceType.absent"), // + APPROVED(3, "attendanceType.approved"), // + LEAVE(4, "attendanceType.leave"), // + LATE(5, "attendanceType.late"); + + private final Integer value; + private final String code; + + private AttendanceType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return code; + } + + public static AttendanceType fromInt(final Integer attendanceTypeId) { + + if (attendanceTypeId == null) { return AttendanceType.INVALID; } + + AttendanceType attendanceType = AttendanceType.INVALID; + switch (attendanceTypeId) { + case 1: + attendanceType = AttendanceType.PRESENT; + break; + case 2: + attendanceType = AttendanceType.ABSENT; + break; + case 3: + attendanceType = AttendanceType.APPROVED; + break; + case 4: + attendanceType = AttendanceType.LEAVE; + break; + case 5: + attendanceType = AttendanceType.LATE; + break; + } + return attendanceType; + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/data/ClientAttendanceData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/data/ClientAttendanceData.java new file mode 100644 index 000000000..4533d4eb7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/data/ClientAttendanceData.java @@ -0,0 +1,35 @@ +/** + * 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.meeting.attendance.data; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; + +/** + * Immutable data object representing a ClientAttendance. + */ +public class ClientAttendanceData { + + @SuppressWarnings("unused") + private final Long id; + @SuppressWarnings("unused") + private final Long clientId; + @SuppressWarnings("unused") + private final String clientName; + @SuppressWarnings("unused") + private final EnumOptionData attendanceType; + + public static ClientAttendanceData instance(Long id, Long clientId, String clientName, EnumOptionData attendanceType){ + return new ClientAttendanceData(id, clientId, clientName, attendanceType); + } + + private ClientAttendanceData(Long id, Long clientId, String clientName, EnumOptionData attendanceType) { + this.id = id; + this.clientId = clientId; + this.clientName = clientName; + this.attendanceType = attendanceType; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendance.java new file mode 100644 index 000000000..e7c07ae75 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendance.java @@ -0,0 +1,54 @@ +package org.mifosplatform.portfolio.meeting.attendance.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.meeting.domain.Meeting; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_client_attendance", uniqueConstraints = { @UniqueConstraint(columnNames = { "client_id", "meeting_id" }, name = "unique_client_meeting_attendance") }) +public class ClientAttendance extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "client_id", nullable = false) + private Client client; + + @ManyToOne + @JoinColumn(name = "meeting_id", nullable = false) + private Meeting meeting; + + @Column(name = "attendance_type_enum", nullable = false) + private Integer attendanceTypeId; + + protected ClientAttendance() { + + } + + public static ClientAttendance createClientAttendance(final Client client, final Meeting meeting, final Integer attendanceTypeId) { + return new ClientAttendance(client, meeting, attendanceTypeId); + } + + private ClientAttendance(final Client client, final Meeting meeting, final Integer attendanceTypeId) { + this.client = client; + this.meeting = meeting; + this.attendanceTypeId = attendanceTypeId; + } + + public Long clientId() { + return client.getId(); + } + + public void updateAttendanceTypeId(final Integer attendanceTypeId) { + this.attendanceTypeId = attendanceTypeId; + } + + public Integer getAttendanceTypeId() { + return this.attendanceTypeId; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java new file mode 100644 index 000000000..e2615baee --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java @@ -0,0 +1,13 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.attendance.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface ClientAttendanceRepository extends JpaRepository, JpaSpecificationExecutor { + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java new file mode 100644 index 000000000..ee99387c8 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java @@ -0,0 +1,14 @@ +/** + * 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.meeting.attendance.service; + +import java.util.List; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; + +public interface AttendanceDropdownReadPlatformService { + List retrieveAttendanceTypeOptions(); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java new file mode 100644 index 000000000..86e4dafd9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java @@ -0,0 +1,22 @@ +/** + * 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.meeting.attendance.service; + +import java.util.List; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.meeting.attendance.AttendanceType; +import org.springframework.stereotype.Service; + +@Service +public class AttendanceDropdownReadPlatformServiceImpl implements AttendanceDropdownReadPlatformService { + + @Override + public List retrieveAttendanceTypeOptions() { + return AttendanceEnumerations.attendanceType(AttendanceType.values()); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceEnumerations.java new file mode 100644 index 000000000..258739ab9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceEnumerations.java @@ -0,0 +1,57 @@ +/** + * 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.meeting.attendance.service; + +import java.util.ArrayList; +import java.util.List; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.meeting.attendance.AttendanceType; + +public class AttendanceEnumerations { + + public static EnumOptionData attendanceType(final int attendanceType) { + return attendanceType(AttendanceType.fromInt(attendanceType)); + } + + public static EnumOptionData attendanceType(final AttendanceType attendanceType) { + + EnumOptionData optionData = new EnumOptionData(AttendanceType.INVALID.getValue().longValue(), AttendanceType.INVALID.getCode(), + "Invalid"); + + switch (attendanceType) { + case INVALID: + optionData = new EnumOptionData(AttendanceType.INVALID.getValue().longValue(), AttendanceType.INVALID.getCode(), "Invalid"); + break; + case PRESENT: + optionData = new EnumOptionData(AttendanceType.PRESENT.getValue().longValue(), AttendanceType.PRESENT.getCode(), "Present"); + break; + case ABSENT: + optionData = new EnumOptionData(AttendanceType.ABSENT.getValue().longValue(), AttendanceType.ABSENT.getCode(), "Absent"); + break; + case APPROVED: + optionData = new EnumOptionData(AttendanceType.APPROVED.getValue().longValue(), AttendanceType.APPROVED.getCode(), + "Approved"); + break; + case LEAVE: + optionData = new EnumOptionData(AttendanceType.LEAVE.getValue().longValue(), AttendanceType.LEAVE.getCode(), "Leave"); + break; + case LATE: + optionData = new EnumOptionData(AttendanceType.LATE.getValue().longValue(), AttendanceType.LATE.getCode(), "Late"); + break; + } + return optionData; + } + + public static List attendanceType(final AttendanceType[] attendanceTypes) { + final List optionDatas = new ArrayList(); + for (final AttendanceType attendanceType : attendanceTypes) { + if(attendanceType.getValue().equals(AttendanceType.INVALID.getValue())) continue; + optionDatas.add(attendanceType(attendanceType)); + } + return optionDatas; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java new file mode 100644 index 000000000..77a22ea54 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java @@ -0,0 +1,14 @@ +/** + * 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.meeting.attendance.service; + +import java.util.Collection; + +import org.mifosplatform.portfolio.meeting.attendance.data.ClientAttendanceData; + +public interface ClientAttendanceReadPlatformService { + Collection retrieveClientAttendanceByMeetingId(final Long meetingId); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java new file mode 100644 index 000000000..a28c7365e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java @@ -0,0 +1,59 @@ +/** + * 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.meeting.attendance.service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.portfolio.meeting.attendance.data.ClientAttendanceData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class ClientAttendanceReadPlatformServiceImpl implements ClientAttendanceReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public ClientAttendanceReadPlatformServiceImpl(final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + private static final class ClientAttendanceDataMapper implements RowMapper { + + public String schema() { + + return " select ca.id as id, ca.client_id as clientId, ca.attendance_type_enum as attendanceTypeId, " + + " c.display_name as clientName from m_meeting m inner join m_client_attendance ca on m.id = ca.meeting_id " + + " inner join m_client c on ca.client_id=c.id "; + } + + @Override + public ClientAttendanceData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong("id"); + final Long clientId = rs.getLong("clientId"); + final Integer attendanceTypeId = rs.getInt("attendanceTypeId"); + final String clientName = rs.getString("clientName"); + final EnumOptionData attendanceType = AttendanceEnumerations.attendanceType(attendanceTypeId); + return ClientAttendanceData.instance(id, clientId, clientName, attendanceType); + } + } + + @Override + public Collection retrieveClientAttendanceByMeetingId(final Long meetingId) { + final ClientAttendanceDataMapper rm = new ClientAttendanceDataMapper(); + final String sql = rm.schema() + " where m.id = ? "; + + return this.jdbcTemplate.query(sql, rm, new Object[] { meetingId }); + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingData.java new file mode 100644 index 000000000..8fd10081f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingData.java @@ -0,0 +1,69 @@ +/** + * 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.meeting.data; + +import java.util.Collection; +import java.util.List; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.calendar.data.CalendarData; +import org.mifosplatform.portfolio.client.data.ClientData; +import org.mifosplatform.portfolio.meeting.attendance.data.ClientAttendanceData; + +/** + * Immutable data object representing a Meeting. + */ +public class MeetingData { + + private final Long id; + private final LocalDate meetingDate; + private final Collection clientsAttendance; + + // template data + private final Collection clients; + private final CalendarData calendarData; + @SuppressWarnings("unused") + private final List attendanceTypeOptions; + + public static MeetingData instance(final Long id, final LocalDate meetingDate) { + final Collection clientsAttendance = null; + final Collection clients = null; + final CalendarData calendarData = null; + final List attendanceTypeOptions = null; + return new MeetingData(id, meetingDate, clientsAttendance, clients, calendarData, attendanceTypeOptions); + } + + public static MeetingData withClientsAttendanceAndAttendanceTypeOptions(final MeetingData meetingData, + final Collection clientsAttendance, final List attendanceTypesOptions) { + return new MeetingData(meetingData.id, meetingData.meetingDate, clientsAttendance, meetingData.clients, meetingData.calendarData, + attendanceTypesOptions); + } + + public static MeetingData template(final Collection clients, final CalendarData calendarData, + final List attendanceTypeOptions) { + final Long id = null; + final LocalDate meetingDate = null; + final Collection clientsAttendance = null; + return new MeetingData(id, meetingDate, clientsAttendance, clients, calendarData, attendanceTypeOptions); + } + + public static MeetingData withAttendanceTypeOptions(final MeetingData meetingData, final List attendanceTypeOptions) { + + return new MeetingData(meetingData.id, meetingData.meetingDate, meetingData.clientsAttendance, meetingData.clients, + meetingData.calendarData, attendanceTypeOptions); + } + + private MeetingData(Long id, LocalDate meetingDate, Collection clientsAttendance, + final Collection clients, final CalendarData calendarData, final List attendanceTypeOptions) { + this.id = id; + this.meetingDate = meetingDate; + this.clientsAttendance = clientsAttendance; + this.clients = clients; + this.calendarData = calendarData; + this.attendanceTypeOptions = attendanceTypeOptions; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingDataValidator.java new file mode 100644 index 000000000..aab73bdaf --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingDataValidator.java @@ -0,0 +1,144 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.data; + +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.MEETING_REQUEST_DATA_PARAMETERS; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.MEETING_RESOURCE_NAME; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.calendarIdParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientIdParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.meetingDateParamName; + +import java.lang.reflect.Type; +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.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; + +@Component +public class MeetingDataValidator { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public MeetingDataValidator(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForCreate(final JsonCommand command) { + final String json = command.json(); + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(MEETING_RESOURCE_NAME); + final JsonElement element = fromApiJsonHelper.parse(json); + + final String meetingDateStr = this.fromApiJsonHelper.extractStringNamed(meetingDateParamName, element); + baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDateStr).notBlank(); + + if(!StringUtils.isBlank(meetingDateStr)){ + final LocalDate meetingDate = this.fromApiJsonHelper.extractLocalDateNamed(meetingDateParamName, element); + baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDate).notNull(); + } + + final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParamName, element); + baseDataValidator.reset().parameter(calendarIdParamName).value(calendarId).notNull(); + + validateAttendanceDetails(element, baseDataValidator); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForUpdate(final JsonCommand command) { + final String json = command.json(); + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(MEETING_RESOURCE_NAME); + final JsonElement element = fromApiJsonHelper.parse(json); + + if (this.fromApiJsonHelper.parameterExists(meetingDateParamName, element)) { + final String meetingDateStr = this.fromApiJsonHelper.extractStringNamed(meetingDateParamName, element); + baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDateStr).notBlank(); + + if(!StringUtils.isBlank(meetingDateStr)){ + final LocalDate meetingDate = this.fromApiJsonHelper.extractLocalDateNamed(meetingDateParamName, element); + baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDate).notNull(); + } + } + + if(this.fromApiJsonHelper.parameterExists(calendarIdParamName, element)){ + final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParamName, element); + baseDataValidator.reset().parameter(calendarIdParamName).value(calendarId).notNull(); + } + + validateAttendanceDetails(element, baseDataValidator); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForUpdateAttendance(final JsonCommand command) { + final String json = command.json(); + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(MEETING_RESOURCE_NAME); + final JsonElement element = fromApiJsonHelper.parse(json); + + validateAttendanceDetails(element, baseDataValidator); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + private void validateAttendanceDetails(final JsonElement element, final DataValidatorBuilder baseDataValidator){ + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + if (element.isJsonObject()) { + if (topLevelJsonElement.has(clientsAttendanceParamName) + && topLevelJsonElement.get(clientsAttendanceParamName).isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(clientsAttendanceParamName).getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + final JsonObject attendanceElement = array.get(i).getAsJsonObject(); + final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, attendanceElement); + final Long attendanceType = this.fromApiJsonHelper.extractLongNamed(attendanceTypeParamName, attendanceElement); + baseDataValidator.reset().parameter(clientsAttendanceParamName + "[" + i + "]." + clientIdParamName).value(clientId).notNull().integerGreaterThanZero(); + baseDataValidator.reset().parameter(clientsAttendanceParamName + "[" + i + "]." + attendanceTypeParamName).value(attendanceType).notNull().integerGreaterThanZero(); + } + } + } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/Meeting.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/Meeting.java new file mode 100644 index 000000000..1dcabee46 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/Meeting.java @@ -0,0 +1,188 @@ +/** + * 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.meeting.domain; + +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientIdParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.meetingDateParamName; + +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.portfolio.calendar.domain.Calendar; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; +import org.mifosplatform.portfolio.meeting.attendance.domain.ClientAttendance; +import org.mifosplatform.portfolio.meeting.exception.MeetingDateException; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_meeting", uniqueConstraints = { @UniqueConstraint(columnNames = { "calendar_instance_id", "meeting_date" }, name = "unique_calendar_instance_id_meeting_date") }) +public class Meeting extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "calendar_instance_id", nullable = false) + private CalendarInstance calendarInstance; + + @Column(name = "meeting_date", nullable = false) + @Temporal(TemporalType.DATE) + private Date meetingDate; + + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "meeting", orphanRemoval = true) + private Set clientsAttendance; + + protected Meeting() { + // + } + + private Meeting(CalendarInstance calendarInstance, Date meetingDate) { + this.calendarInstance = calendarInstance; + this.meetingDate = meetingDate; + } + + public static Meeting createNew(final CalendarInstance calendarInstance, final Date meetingDate) { + + if(!isValidMeetingDate(calendarInstance, meetingDate)){ + throw new NotValidRecurringDateException("meeting", "The date '" + meetingDate + "' is not a valid meeting date.", meetingDate); + } + return new Meeting(calendarInstance, meetingDate); + } + + public void associateClientsAttendance(final Collection clientsAttendance) { + //do not allow to capture attendance in advance. + if(isMeetingDateAfter(DateUtils.getLocalDateOfTenant())){ + final String errorMessage = "Attendance cannot be in the future."; + throw new MeetingDateException("cannot.be.a.future.date", errorMessage, getMeetingDateLocalDate()); + } + this.clientsAttendance = new HashSet(clientsAttendance); + } + + public Map update(final JsonCommand command) { + final Map actualChanges = new LinkedHashMap(9); + final String dateFormatAsInput = command.dateFormat(); + final String localeAsInput = command.locale(); + + if (command.isChangeInLocalDateParameterNamed(meetingDateParamName, getMeetingDateLocalDate())) { + final String valueAsInput = command.stringValueOfParameterNamed(meetingDateParamName); + final LocalDate newValue = command.localDateValueOfParameterNamed(meetingDateParamName); + actualChanges.put(meetingDateParamName, valueAsInput); + actualChanges.put("dateFormat", dateFormatAsInput); + actualChanges.put("locale", localeAsInput); + this.meetingDate = newValue.toDate(); + + if(!isValidMeetingDate(this.calendarInstance, this.meetingDate)){ + throw new NotValidRecurringDateException("meeting", "Not a valid meeting date", meetingDate); + } + + } + + return actualChanges; + } + + public Map updateAttendance(final Collection clientsAttendance) { + final Map actualChanges = new LinkedHashMap(1); + final Map clientAttendanceChanges = new LinkedHashMap(clientsAttendance.size()); + + updateAttendanceLoop: for (ClientAttendance clientAttendance : clientsAttendance) { + for (ClientAttendance clientAttendanceOriginal : this.clientsAttendance) { + if (clientAttendanceOriginal.clientId().equals(clientAttendance.clientId())) { + final Integer newValue = clientAttendance.getAttendanceTypeId(); + if(!newValue.equals(clientAttendanceOriginal.getAttendanceTypeId())){ + clientAttendanceOriginal.updateAttendanceTypeId(newValue); + final Map clientAttendanceChange = new LinkedHashMap(2); + clientAttendanceChange.put(clientIdParamName, clientAttendanceOriginal.clientId()); + clientAttendanceChange.put(attendanceTypeParamName, newValue); + clientAttendanceChanges.put(clientAttendanceOriginal.clientId().toString(), clientAttendanceChange); + } + continue updateAttendanceLoop; + } + } + + final Map clientAttendanceChange = new LinkedHashMap(); + clientAttendanceChange.put(clientIdParamName, clientAttendance.clientId()); + clientAttendanceChange.put(attendanceTypeParamName, clientAttendance.getAttendanceTypeId()); + clientAttendanceChanges.put(clientAttendance.clientId().toString(), clientAttendanceChange); + //New attendance record + this.clientsAttendance.add(clientAttendance); + } + + actualChanges.put(clientsAttendanceParamName, clientAttendanceChanges); + + return actualChanges; + } + + public Long entityId() { + return calendarInstance.getEntityId(); + } + + public boolean isCenterEntity(){ + return CalendarEntityType.isCenter(calendarInstance.getEntityTypeId()); + } + + public boolean isGroupEntity(){ + return CalendarEntityType.isGroup(calendarInstance.getEntityTypeId()); + } + + public LocalDate getMeetingDateLocalDate() { + LocalDate meetingDateLocalDate = null; + if (this.meetingDate != null) { + meetingDateLocalDate = LocalDate.fromDateFields(this.meetingDate); + } + return meetingDateLocalDate; + } + + public Date getMeetingDate(){ + return this.meetingDate; + } + + public boolean isMeetingDateBefore(final LocalDate newStartDate) { + return (this.meetingDate != null && newStartDate != null && getMeetingDateLocalDate().isBefore(newStartDate)) ? true : false; + } + + private static boolean isValidMeetingDate(final CalendarInstance calendarInstance, final Date meetingDate ){ + final Calendar calendar = calendarInstance.getCalendar(); + LocalDate meetingDateLocalDate = null; + if (meetingDate != null) { + meetingDateLocalDate = LocalDate.fromDateFields(meetingDate); + } + if(meetingDateLocalDate == null || !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), meetingDateLocalDate)){ + return false; + } + return true; + } + + private boolean isMeetingDateAfter(final LocalDate date){ + return getMeetingDateLocalDate().isAfter(date); + } + + public Collection getClientsAttendance() { + return this.clientsAttendance; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepository.java new file mode 100644 index 000000000..d13b66970 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepository.java @@ -0,0 +1,13 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface MeetingRepository extends JpaRepository, JpaSpecificationExecutor { + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepositoryWrapper.java new file mode 100644 index 000000000..e80292a62 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepositoryWrapper.java @@ -0,0 +1,51 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.domain; + +import org.mifosplatform.portfolio.meeting.exception.MeetingNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * Wrapper for {@link MeetingRepository} that is responsible for checking + * if {@link Meeting} is returned when using findOne + * repository method and throwing an appropriate not found exception. + *

+ * + *

+ * This is to avoid need for checking and throwing in multiple areas of code + * base where {@link MeetingRepository} is required. + *

+ */ +@Service +public class MeetingRepositoryWrapper { + + private final MeetingRepository repository; + + @Autowired + public MeetingRepositoryWrapper(final MeetingRepository repository) { + this.repository = repository; + } + + public Meeting findOneWithNotFoundDetection(final Long meetingId) { + final Meeting meeting = this.repository.findOne(meetingId); + if (meeting == null) { throw new MeetingNotFoundException(meetingId); } + return meeting; + } + + public void save(final Meeting meeting) { + this.repository.save(meeting); + } + + public void delete(final Meeting meeting) { + this.repository.delete(meeting); + } + + public void saveAndFlush(final Meeting meeting) { + this.repository.saveAndFlush(meeting); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingDateException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingDateException.java new file mode 100644 index 000000000..5b3dd2c0f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingDateException.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * A {@link RuntimeException} thrown when Meeting date violates a domain rule. + */ +public class MeetingDateException extends AbstractPlatformDomainRuleException { + + public MeetingDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) { + super("error.msg.meeting.date." + postFix, defaultUserMessage, defaultUserMessageArgs); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotFoundException.java new file mode 100644 index 000000000..b5e200b93 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotFoundException.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +/** + * A {@link RuntimeException} thrown when Meeting resources are not found. + */ +public class MeetingNotFoundException extends AbstractPlatformResourceNotFoundException { + + public MeetingNotFoundException(final Long id) { + super("error.msg.meeting.id.invalid", "Meeting with identifier " + id + " does not exist", id); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotSupportedResourceException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotSupportedResourceException.java new file mode 100644 index 000000000..e240aeea5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotSupportedResourceException.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * {@link AbstractPlatformDomainRuleException} thrown when meeting is captured against not supported resource. + */ +public class MeetingNotSupportedResourceException extends AbstractPlatformDomainRuleException { + + public MeetingNotSupportedResourceException(String defaultUserMessage, Object... defaultUserMessageArgs) { + super("error.msg.meeting.not.supported.resource", defaultUserMessage, defaultUserMessageArgs); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/CreateMeetingCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/CreateMeetingCommandHandler.java new file mode 100644 index 000000000..690817f59 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/CreateMeetingCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.meeting.service.MeetingWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CreateMeetingCommandHandler implements NewCommandSourceHandler { + + private final MeetingWritePlatformService writePlatformService; + + @Autowired + public CreateMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.createMeeting(command); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/DeleteMeetingCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/DeleteMeetingCommandHandler.java new file mode 100644 index 000000000..cf1ef32ed --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/DeleteMeetingCommandHandler.java @@ -0,0 +1,31 @@ +/** + * 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.meeting.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.meeting.service.MeetingWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class DeleteMeetingCommandHandler implements NewCommandSourceHandler { + + private final MeetingWritePlatformService writePlatformService; + + @Autowired + public DeleteMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.deleteMeeting(command.entityId()); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java new file mode 100644 index 000000000..cf52a63e0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java @@ -0,0 +1,33 @@ +/** + * 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.meeting.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.meeting.service.MeetingWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UpdateMeetingAttendanceCommandHandler implements NewCommandSourceHandler { + + private final MeetingWritePlatformService writePlatformService; + + @Autowired + public UpdateMeetingAttendanceCommandHandler(final MeetingWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.saveOrUpdateAttendance(command); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingCommandHandler.java new file mode 100644 index 000000000..208eeac77 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingCommandHandler.java @@ -0,0 +1,33 @@ +/** + * 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.meeting.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.meeting.service.MeetingWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UpdateMeetingCommandHandler implements NewCommandSourceHandler { + + private final MeetingWritePlatformService writePlatformService; + + @Autowired + public UpdateMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.updateMeeting(command); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformService.java new file mode 100644 index 000000000..05350ccd5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformService.java @@ -0,0 +1,21 @@ +/** + * 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.meeting.service; + +import java.util.Collection; +import java.util.List; + +import org.mifosplatform.portfolio.meeting.data.MeetingData; + +public interface MeetingReadPlatformService { + + MeetingData retrieveMeeting(final Long meetingId, Long entityId, Integer entityTypeId); + + Collection retrieveMeetingsByEntity(final Long entityId, final Integer entityTypeId, Integer limit); + + Collection retrieveMeetingsByEntityByCalendarType(final Long entityId, final Integer entityTypeId, + final List calendarTypeOptions); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java new file mode 100644 index 000000000..9904e5b7f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java @@ -0,0 +1,89 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.domain.JdbcSupport; +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; +import org.mifosplatform.portfolio.meeting.data.MeetingData; +import org.mifosplatform.portfolio.meeting.exception.MeetingNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class MeetingReadPlatformServiceImpl implements MeetingReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public MeetingReadPlatformServiceImpl(final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + private static final class MeetingDataMapper implements RowMapper { + + public String schema() { + + return " select m.id as id, m.meeting_date as meetingDate from m_meeting m inner join m_calendar_instance ci on m.calendar_instance_id = ci.id "; + } + + @Override + public MeetingData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong("id"); + final LocalDate meetingDate = JdbcSupport.getLocalDate(rs, "meetingDate"); + + return MeetingData.instance(id, meetingDate); + } + } + + @Override + public MeetingData retrieveMeeting(final Long meetingId, Long entityId, Integer entityTypeId) { + + try { + final MeetingDataMapper rm = new MeetingDataMapper(); + + final String sql = rm.schema() + " where m.id = ? and ci.entity_id = ? and ci.entity_type_enum = ? "; + + return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { meetingId, entityId, entityTypeId }); + } catch (final EmptyResultDataAccessException e) { + throw new MeetingNotFoundException(meetingId); + } + } + + @Override + public Collection retrieveMeetingsByEntity(Long entityId, Integer entityTypeId, Integer limit) { + final MeetingDataMapper rm = new MeetingDataMapper(); + String sql = rm.schema() + " where ci.entity_id = ? and ci.entity_type_enum = ? "; + if(limit != null && limit > 0){ + sql = sql + " order by m.meeting_date desc " + " limit " + limit; + } + + return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId }); + } + + @Override + public Collection retrieveMeetingsByEntityByCalendarType(Long entityId, Integer entityTypeId, + List calendarTypeOptions) { + final MeetingDataMapper rm = new MeetingDataMapper(); + String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions); + final String sql = rm.schema() + + " inner join m_calendar c on ci.calendar_id=c.id where ci.entity_id = ? and ci.entity_type_enum = ? and c.calendar_type_enum in (" + + sqlCalendarTypeOptions + ") order by c.start_date "; + + return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId }); + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformService.java new file mode 100644 index 000000000..5d7ac4952 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformService.java @@ -0,0 +1,20 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.meeting.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public interface MeetingWritePlatformService { + + CommandProcessingResult createMeeting(JsonCommand command); + + CommandProcessingResult updateMeeting(JsonCommand command); + + CommandProcessingResult deleteMeeting(Long meetingId); + + CommandProcessingResult saveOrUpdateAttendance(JsonCommand command); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..f4b39854c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,229 @@ +/** + * 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.meeting.service; + +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.calendarIdParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientIdParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName; +import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.meetingDateParamName; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.calendar.domain.Calendar; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; +import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; +import org.mifosplatform.portfolio.calendar.exception.CalendarInstanceNotFoundException; +import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.client.domain.ClientRepository; +import org.mifosplatform.portfolio.group.domain.Group; +import org.mifosplatform.portfolio.group.domain.GroupRepository; +import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException; +import org.mifosplatform.portfolio.meeting.attendance.domain.ClientAttendance; +import org.mifosplatform.portfolio.meeting.data.MeetingDataValidator; +import org.mifosplatform.portfolio.meeting.domain.Meeting; +import org.mifosplatform.portfolio.meeting.domain.MeetingRepositoryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +@Service +public class MeetingWritePlatformServiceJpaRepositoryImpl implements MeetingWritePlatformService { + + private final MeetingRepositoryWrapper meetingRepository; + private final MeetingDataValidator meetingDataValidator; + private final CalendarInstanceRepository calendarInstanceRepository; + private final CalendarRepository calendarRepository; + private final ClientRepository clientRepository; + private final GroupRepository groupRepository; + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public MeetingWritePlatformServiceJpaRepositoryImpl(MeetingRepositoryWrapper meetingRepository, + MeetingDataValidator meetingDataValidator, CalendarInstanceRepository calendarInstanceRepository, + CalendarRepository calendarRepository, final ClientRepository clientRepository, final GroupRepository groupRepository, + final FromJsonHelper fromApiJsonHelper) { + this.meetingRepository = meetingRepository; + this.meetingDataValidator = meetingDataValidator; + this.calendarInstanceRepository = calendarInstanceRepository; + this.calendarRepository = calendarRepository; + this.clientRepository = clientRepository; + this.groupRepository = groupRepository; + this.fromApiJsonHelper = fromApiJsonHelper; + } + + @Override + public CommandProcessingResult createMeeting(JsonCommand command) { + + this.meetingDataValidator.validateForCreate(command); + + final Date meetingDate = command.DateValueOfParameterNamed(meetingDateParamName); + + try { + final CalendarInstance calendarInstance = getCalendarInstance(command); + // create new meeting + final Meeting newMeeting = Meeting.createNew(calendarInstance, meetingDate); + + final Collection clientsAttendance = getClientsAttendance(newMeeting, command); + if (clientsAttendance != null && !clientsAttendance.isEmpty()) { + newMeeting.associateClientsAttendance(clientsAttendance); + } + // save meeting details + this.meetingRepository.save(newMeeting); + final Long groupId = newMeeting.isGroupEntity() ? newMeeting.entityId() : null; + return new CommandProcessingResultBuilder() // + .withEntityId(newMeeting.getId()) // + .withGroupId(groupId).build(); + + } catch (DataIntegrityViolationException dve) { + handleMeetingDataIntegrityIssues(meetingDate, dve); + return new CommandProcessingResultBuilder() // + .build(); + } + } + + private CalendarInstance getCalendarInstance(final JsonCommand command) { + final Long calendarId = command.longValueOfParameterNamed(calendarIdParamName); + final Long entityId = command.getSupportedEntityId(); + final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId); + if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); } + + final Integer entityTypeId = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue(); + final CalendarInstance calendarInstance = calendarInstanceRepository.findByCalendarIdAndEntityIdAndEntityTypeId( + calendarForUpdate.getId(), entityId, entityTypeId); + if(calendarInstance == null) { + final String postFix = "for." + command.getSupportedEntityType() + "not.found"; + final String defaultUserMessage = "No Calendar Instance details found for group with identifier " + entityId + " and calendar with identifier " + calendarId; + throw new CalendarInstanceNotFoundException(postFix, defaultUserMessage, entityId, calendarId); + } + return calendarInstance; + } + + private Collection getClientsAttendance(final Meeting meeting, final JsonCommand command) { + final Collection clientsAttendance = new ArrayList(); + + Collection childGroups = null; + if (meeting.isCenterEntity()) { + childGroups = this.groupRepository.findByParentId(meeting.entityId()); + } + + final String json = command.json(); + final JsonElement element = this.fromApiJsonHelper.parse(json); + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + if (element.isJsonObject()) { + if (topLevelJsonElement.has(clientsAttendanceParamName) && topLevelJsonElement.get(clientsAttendanceParamName).isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(clientsAttendanceParamName).getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + final JsonObject attendanceElement = array.get(i).getAsJsonObject(); + final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, attendanceElement); + final Integer attendanceTypeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(attendanceTypeParamName, + attendanceElement); + + final Client client = this.clientRepository.findOne(clientId); + + if (meeting.isGroupEntity() && !client.isChildOfGroup(meeting.entityId())) { + throw new ClientNotInGroupException(clientId, meeting.entityId()); + } else if (meeting.isCenterEntity()) { + if (childGroups != null && !childGroups.isEmpty()) { + boolean isChildClient = false; + for (Group group : childGroups) { + if (group.isChildClient(clientId)){ + isChildClient = true; + break; + } + } + if(!isChildClient){ + final String defaultUserMessage = "Client with identifier " + clientId + " is not in center " + + meeting.entityId(); + throw new ClientNotInGroupException("client.not.in.center", defaultUserMessage, clientId, meeting.entityId()); + } + } + } + + final ClientAttendance clientAttendance = ClientAttendance.createClientAttendance(client, meeting, attendanceTypeId); + clientsAttendance.add(clientAttendance); + } + } + } + return clientsAttendance; + } + + @Override + public CommandProcessingResult updateMeeting(JsonCommand command) { + this.meetingDataValidator.validateForUpdate(command); + + final Meeting meetingForUpdate = this.meetingRepository.findOneWithNotFoundDetection(command.entityId()); + final Map changes = meetingForUpdate.update(command); + + try { + if (!changes.isEmpty()) { + this.meetingRepository.saveAndFlush(meetingForUpdate); + } + } catch (DataIntegrityViolationException dve) { + handleMeetingDataIntegrityIssues(meetingForUpdate.getMeetingDate(), dve); + return new CommandProcessingResultBuilder() // + .build(); + } + final Long groupId = meetingForUpdate.isGroupEntity() ? meetingForUpdate.entityId() : null; + return new CommandProcessingResultBuilder() // + .withEntityId(meetingForUpdate.getId()) // + .withGroupId(groupId) // + .with(changes) // + .build(); + } + + @Override + public CommandProcessingResult deleteMeeting(Long meetingId) { + final Meeting meetingForDelete = this.meetingRepository.findOneWithNotFoundDetection(meetingId); + this.meetingRepository.delete(meetingForDelete); + return new CommandProcessingResultBuilder() // + .withEntityId(meetingId) // + .build(); + } + + @Override + public CommandProcessingResult saveOrUpdateAttendance(JsonCommand command) { + this.meetingDataValidator.validateForUpdateAttendance(command); + + final Meeting meetingForUpdate = this.meetingRepository.findOneWithNotFoundDetection(command.entityId()); + final Collection clientsAttendance = getClientsAttendance(meetingForUpdate, command); + final Map changes = meetingForUpdate.updateAttendance(clientsAttendance); + + this.meetingRepository.saveAndFlush(meetingForUpdate); + final Long groupId = meetingForUpdate.isGroupEntity() ? meetingForUpdate.entityId() : null; + return new CommandProcessingResultBuilder() // + .withEntityId(meetingForUpdate.getId()) // + .withGroupId(groupId) // + .with(changes) // + .build(); + } + + private void handleMeetingDataIntegrityIssues(final Date meetingDate, final DataIntegrityViolationException dve) { + final Throwable realCause = dve.getMostSpecificCause(); + if (realCause.getMessage().contains("unique_calendar_instance_id_meeting_date")) { + + throw new PlatformDataIntegrityException("error.msg.meeting.duplicate", "A meeting with date '" + meetingDate + "' already exists", + meetingDateParamName, meetingDate); } + + throw new PlatformDataIntegrityException("error.msg.meeting.unknown.data.integrity.issue", + "Unknown data integrity issue with resource: " + realCause.getMessage()); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/api/NotesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/api/NotesApiResource.java index 5fabdf8ff..65780aa82 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/api/NotesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/api/NotesApiResource.java @@ -70,10 +70,9 @@ public class NotesApiResource { public String retrieveNotesByResource(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) { - NoteType noteType = NoteType.fromApiUrl(resourceType); + final NoteType noteType = NoteType.fromApiUrl(resourceType); if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); } - ; this.context.authenticatedUser().validateHasReadPermission(getResourceNameForPermissions(noteType)); @@ -92,10 +91,9 @@ public class NotesApiResource { public String retrieveNote(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId, @PathParam("noteId") final Long noteId, @Context final UriInfo uriInfo) { - NoteType noteType = NoteType.fromApiUrl(resourceType); + final NoteType noteType = NoteType.fromApiUrl(resourceType); if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); } - ; this.context.authenticatedUser().validateHasReadPermission(getResourceNameForPermissions(noteType)); @@ -113,10 +111,9 @@ public class NotesApiResource { public String addNewNote(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId, final String apiRequestBodyAsJson) { - NoteType noteType = NoteType.fromApiUrl(resourceType); + final NoteType noteType = NoteType.fromApiUrl(resourceType); if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); } - ; final String resourceNameForPermissions = getResourceNameForPermissions(noteType); final CommandWrapper commandRequest = new CommandWrapperBuilder().createNote(resourceNameForPermissions, resourceType, resourceId) @@ -134,10 +131,9 @@ public class NotesApiResource { public String updateNote(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId, @PathParam("noteId") final Long noteId, final String apiRequestBodyAsJson) { - NoteType noteType = NoteType.fromApiUrl(resourceType); + final NoteType noteType = NoteType.fromApiUrl(resourceType); if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); } - ; final String resourceNameForPermissions = getResourceNameForPermissions(noteType); @@ -156,10 +152,9 @@ public class NotesApiResource { public String deleteNote(@PathParam("resourceType") final String resourceType, @PathParam("resourceId") final Long resourceId, @PathParam("noteId") final Long noteId) { - NoteType noteType = NoteType.fromApiUrl(resourceType); + final NoteType noteType = NoteType.fromApiUrl(resourceType); if (noteType == null) { throw new NoteResourceNotSupportedException(resourceType); } - ; final String resourceNameForPermissions = getResourceNameForPermissions(noteType); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java index 8e449d281..ead9f7cf8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java @@ -32,17 +32,14 @@ public class Note extends AbstractAuditableCustom { @JoinColumn(name = "client_id", nullable = true) private final Client client; - @SuppressWarnings("unused") @ManyToOne @JoinColumn(name = "group_id", nullable = true) private Group group; - @SuppressWarnings("unused") @ManyToOne @JoinColumn(name = "loan_id", nullable = true) private Loan loan; - @SuppressWarnings("unused") @ManyToOne @JoinColumn(name = "loan_transaction_id", nullable = true) private LoanTransaction loanTransaction; @@ -50,11 +47,9 @@ public class Note extends AbstractAuditableCustom { @Column(name = "note", length = 1000) private String note; - @SuppressWarnings("unused") @Column(name = "note_type_enum") private final Integer noteTypeId; - @SuppressWarnings("unused") @ManyToOne @JoinColumn(name = "savings_account_id", nullable = true) private SavingsAccount savingsAccount; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index b2ba454b1..a3f919e3e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -30,6 +30,11 @@ public class SavingsApiConstants { public static String deleteApplicationAction = ".delete"; public static String undoTransactionAction = ".undotransaction"; public static String applyAnnualFeeTransactionAction = ".applyannualfee"; + public static String adjustTransactionAction = ".adjusttransaction"; + + // command + public static String COMMAND_UNDO_TRANSACTION = "undo"; + public static String COMMAND_ADJUST_TRANSACTION = "modify"; // general public static final String localeParamName = "locale"; @@ -58,6 +63,7 @@ public class SavingsApiConstants { public static final String descriptionParamName = "description"; public static final String currencyCodeParamName = "currencyCode"; public static final String digitsAfterDecimalParamName = "digitsAfterDecimal"; + public static final String inMultiplesOfParamName = "inMultiplesOf"; public static final String nominalAnnualInterestRateParamName = "nominalAnnualInterestRate"; public static final String interestCompoundingPeriodTypeParamName = "interestCompoundingPeriodType"; public static final String interestPostingPeriodTypeParamName = "interestPostingPeriodType"; @@ -81,14 +87,18 @@ public class SavingsApiConstants { // transaction parameters public static final String transactionDateParamName = "transactionDate"; public static final String transactionAmountParamName = "transactionAmount"; + public static final String paymentDetailDataParamName = "paymentDetailData"; + public static final String runningBalanceParamName = "runningBalance"; + public static final String reversedParamName = "reversed"; + public static final String dateParamName = "date"; public static final Set SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, monthDayFormatParamName, nameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, - nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, - interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, - lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, - annualFeeAmountParamName, annualFeeOnMonthDayParamName, accountingRuleParamName, - SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, + interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, + minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, + withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, + accountingRuleParamName, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())); @@ -99,13 +109,13 @@ public class SavingsApiConstants { * parameters to match those of request parameters. */ public static final Set SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, - nameParamName, descriptionParamName, "currency", digitsAfterDecimalParamName, nominalAnnualInterestRateParamName, - interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, - interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, - lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, annualFeeAmountParamName, - annualFeeOnMonthDayParamName, "currencyOptions", "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", - "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", - "withdrawalFeeTypeOptions")); + nameParamName, descriptionParamName, "currency", digitsAfterDecimalParamName, inMultiplesOfParamName, + nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, + interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, + lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, + annualFeeAmountParamName, annualFeeOnMonthDayParamName, "currencyOptions", "interestCompoundingPeriodTypeOptions", + "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions", + "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions")); public static final Set SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, @@ -135,7 +145,8 @@ public class SavingsApiConstants { transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName)); public static final Set SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, - "accountId", accountNoParamName, "currency", "amount")); + "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, runningBalanceParamName, + reversedParamName)); public static final Set SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName)); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java index d02f65cad..8a4f9f87e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java @@ -91,6 +91,27 @@ public class SavingsAccountTransactionsApiResource { SavingsApiConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS); } + @GET + @Path("{transactionId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveOne(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId, + @Context final UriInfo uriInfo) { + + context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); + SavingsAccountTransactionData transactionData = this.savingsAccountReadPlatformService.retrieveSavingsTransaction(savingsId, + transactionId); + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + if (settings.isTemplate()) { + final Collection paymentTypeOptions = codeValueReadPlatformService + .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); + transactionData = SavingsAccountTransactionData.templateOnTop(transactionData, paymentTypeOptions); + } + + return this.toApiJsonSerializer.serialize(settings, transactionData, + SavingsApiConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS); + } + @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @@ -120,7 +141,7 @@ public class SavingsAccountTransactionsApiResource { @Path("{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String undoTransaction(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId, + public String adjustTransaction(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId, @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { String jsonApiRequest = apiRequestBodyAsJson; @@ -131,9 +152,12 @@ public class SavingsAccountTransactionsApiResource { final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); CommandProcessingResult result = null; - if (is(commandParam, "undo")) { + if (is(commandParam, SavingsApiConstants.COMMAND_UNDO_TRANSACTION)) { final CommandWrapper commandRequest = builder.undoSavingsAccountTransaction(savingsId, transactionId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_ADJUST_TRANSACTION)) { + final CommandWrapper commandRequest = builder.adjustSavingsAccountTransaction(savingsId, transactionId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java index e9be89150..e6da94bd1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java @@ -8,6 +8,8 @@ package org.mifosplatform.portfolio.savings.data; import java.math.BigDecimal; import java.util.Collection; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.joda.time.LocalDate; import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; @@ -93,6 +95,26 @@ public class SavingsAccountData { interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); } + public static SavingsAccountData lookup(final Long accountId, final String accountNo) { + + final Collection productOptions = null; + final Collection fieldOfficerOptions = null; + final Collection interestCompoundingPeriodTypeOptions = null; + final Collection interestPostingPeriodTypeOptions = null; + final Collection interestCalculationTypeOptions = null; + final Collection interestCalculationDaysInYearTypeOptions = null; + final Collection lockinPeriodFrequencyTypeOptions = null; + final Collection withdrawalFeeTypeOptions = null; + + final Collection transactions = null; + + return new SavingsAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, transactions, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions); + } + public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template, final Collection transactions) { @@ -263,4 +285,23 @@ public class SavingsAccountData { public Long productId() { return this.savingsProductId; } + + public CurrencyData currency() { + return this.currency; + } + + @Override + public boolean equals(final Object obj) { + + if (obj == null) { return false; } + if (obj == this) { return true; } + if (obj.getClass() != getClass()) { return false; } + SavingsAccountData rhs = (SavingsAccountData) obj; + return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).toHashCode(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java index 28547379e..9dfce4890 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java @@ -11,6 +11,7 @@ import java.util.Collection; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.account.data.AccountTransferData; import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData; /** @@ -29,16 +30,18 @@ public class SavingsAccountTransactionData { private final BigDecimal amount; private final BigDecimal runningBalance; private final boolean reversed; + private final AccountTransferData transfer; // templates final Collection paymentTypeOptions; public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, - final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed) { + final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed, + final AccountTransferData transfer) { final Collection paymentTypeOptions = null; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, - amount, runningBalance, reversed, paymentTypeOptions); + amount, runningBalance, reversed, transfer, paymentTypeOptions); } public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo, @@ -51,7 +54,7 @@ public class SavingsAccountTransactionData { final PaymentDetailData paymentDetailData = null; final Collection paymentTypeOptions = null; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate, - currency, amount, runningBalance, reversed, null); + currency, amount, runningBalance, reversed, null, null); } public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData, @@ -60,13 +63,13 @@ public class SavingsAccountTransactionData { savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId, savingsAccountTransactionData.accountNo, savingsAccountTransactionData.date, savingsAccountTransactionData.currency, savingsAccountTransactionData.amount, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed, - paymentTypeOptions); + savingsAccountTransactionData.transfer, paymentTypeOptions); } private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed, - final Collection paymentTypeOptions) { + final AccountTransferData transfer, final Collection paymentTypeOptions) { this.id = id; this.transactionType = transactionType; this.paymentDetailData = paymentDetailData; @@ -77,6 +80,7 @@ public class SavingsAccountTransactionData { this.amount = amount; this.runningBalance = runningBalance; this.reversed = reversed; + this.transfer = transfer; this.paymentTypeOptions = paymentTypeOptions; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java index f900d1474..7f86492fc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java @@ -12,6 +12,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeO import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; @@ -87,6 +88,9 @@ public class SavingsProductDataValidator { final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element); baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6); + final Integer inMultiplesOf = fromApiJsonHelper.extractIntegerSansLocaleNamed(inMultiplesOfParamName, element); + baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater(); + final BigDecimal nominalAnnualInterestRate = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element); baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate).notNull() @@ -252,6 +256,11 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6); } + if (this.fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) { + final Integer inMultiplesOf = fromApiJsonHelper.extractIntegerSansLocaleNamed(inMultiplesOfParamName, element); + baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater(); + } + if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) { final BigDecimal interestRate = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element); baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 3f187457d..906e87f70 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -58,6 +58,7 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.staff.domain.Staff; +import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; @@ -105,31 +106,29 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "status_enum", nullable = false) private Integer status; + @Column(name = "account_type_enum", nullable = false) + private Integer accountType; + @Temporal(TemporalType.DATE) @Column(name = "submittedon_date", nullable = true) private Date submittedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "submittedon_userid", nullable = true) private AppUser submittedBy; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "rejectedon_date") private Date rejectedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "rejectedon_userid", nullable = true) private AppUser rejectedBy; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "withdrawnon_date") private Date withdrawnOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "withdrawnon_userid", nullable = true) private AppUser withdrawnBy; @@ -138,7 +137,6 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "approvedon_date") private Date approvedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "approvedon_userid", nullable = true) private AppUser approvedBy; @@ -147,17 +145,14 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "activatedon_date", nullable = true) private Date activatedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "activatedon_userid", nullable = true) private AppUser activatedBy; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "closedon_date") private Date closedOnDate; - @SuppressWarnings("unused") @ManyToOne(optional = true) @JoinColumn(name = "closedon_userid", nullable = true) private AppUser closedBy; @@ -254,8 +249,9 @@ public class SavingsAccount extends AbstractPersistable { } public static SavingsAccount createNewApplicationForSubmittal(final Client client, final Group group, final SavingsProduct product, - final Staff fieldOfficer, final String accountNo, final String externalId, final LocalDate submittedOnDate, - final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final Staff fieldOfficer, final String accountNo, final String externalId, final AccountType accountType, + final LocalDate submittedOnDate, final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, @@ -263,15 +259,16 @@ public class SavingsAccount extends AbstractPersistable { final MonthDay annualFeeOnMonthDay) { final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; - return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, submittedOnDate, interestRate, - interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, - annualFeeAmount, annualFeeOnMonthDay); + return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, + interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay); } private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, - final String accountNo, final String externalId, final SavingsAccountStatusType status, final LocalDate submittedOnDate, - final BigDecimal nominalAnnualInterestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final String accountNo, final String externalId, final SavingsAccountStatusType status, final AccountType accountType, + final LocalDate submittedOnDate, final BigDecimal nominalAnnualInterestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, @@ -290,6 +287,7 @@ public class SavingsAccount extends AbstractPersistable { this.currency = product.currency(); this.externalId = externalId; this.status = status.getValue(); + this.accountType = accountType.getValue(); this.submittedOnDate = submittedOnDate.toDate(); this.nominalAnnualInterestRate = nominalAnnualInterestRate; this.interestCompoundingPeriodType = interestCompoundingPeriodType.getValue(); @@ -357,7 +355,7 @@ public class SavingsAccount extends AbstractPersistable { existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - for (PostingPeriod interestPostingPeriod : postingPeriods) { + for (final PostingPeriod interestPostingPeriod : postingPeriods) { final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); @@ -366,14 +364,14 @@ public class SavingsAccount extends AbstractPersistable { interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); - SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); + final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); if (postingTransaction == null) { final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, interestPostingTransactionDate, interestEarnedToBePostedForPeriod); this.transactions.add(newPostingTransaction); recalucateDailyBalanceDetails = true; } else { - boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); + final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); if (correctionRequired) { postingTransaction.reverse(); final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, @@ -403,7 +401,7 @@ public class SavingsAccount extends AbstractPersistable { SavingsAccountTransaction postingTransation = null; - for (SavingsAccountTransaction transaction : this.transactions) { + for (final SavingsAccountTransaction transaction : this.transactions) { if (transaction.isInterestPostingAndNotReversed() && transaction.occursOn(postingDate)) { postingTransation = transaction; break; @@ -450,19 +448,19 @@ public class SavingsAccount extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType .fromInt(this.interestCalculationDaysInYearType); - List postingPeriodIntervals = savingsHelper.determineInterestPostingPeriods(getActivationLocalDate(), + final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(getActivationLocalDate(), upToInterestCalculationDate, postingPeriodType); - List allPostingPeriods = new ArrayList(); + final List allPostingPeriods = new ArrayList(); Money periodStartingBalance = Money.zero(this.currency); final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); - BigDecimal interestRateAsFraction = nominalAnnualInterestRate.divide(BigDecimal.valueOf(100l), mc); - for (LocalDateInterval periodInterval : postingPeriodIntervals) { + final BigDecimal interestRateAsFraction = this.nominalAnnualInterestRate.divide(BigDecimal.valueOf(100l), mc); + for (final LocalDateInterval periodInterval : postingPeriodIntervals) { - PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, - retreiveOrderedListOfTransactions(), currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, + final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, + retreiveOrderedListOfTransactions(), this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue()); periodStartingBalance = postingPeriod.closingBalance(); @@ -470,20 +468,20 @@ public class SavingsAccount extends AbstractPersistable { allPostingPeriods.add(postingPeriod); } - savingsHelper.calculateInterestForAllPostingPeriods(currency, allPostingPeriods); + this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods); - this.summary.updateFromInterestPeriodSummaries(currency, allPostingPeriods); + this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); return allPostingPeriods; } private List retreiveOrderedListOfTransactions() { - List listOfTransactionsSorted = retreiveListOfTransactions(); + final List listOfTransactionsSorted = retreiveListOfTransactions(); - List orderedNonInterestPostingTransactions = new ArrayList(); + final List orderedNonInterestPostingTransactions = new ArrayList(); - for (SavingsAccountTransaction transaction : listOfTransactionsSorted) { + for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { if (!transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) { orderedNonInterestPostingTransactions.add(transaction); } @@ -493,12 +491,12 @@ public class SavingsAccount extends AbstractPersistable { } private List retreiveListOfTransactions() { - List listOfTransactionsSorted = new ArrayList(); - for (SavingsAccountTransaction transaction : this.transactions) { + final List listOfTransactionsSorted = new ArrayList(); + for (final SavingsAccountTransaction transaction : this.transactions) { listOfTransactionsSorted.add(transaction); } - SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator(); + final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator(); Collections.sort(listOfTransactionsSorted, transactionComparator); return listOfTransactionsSorted; @@ -508,9 +506,9 @@ public class SavingsAccount extends AbstractPersistable { Money runningBalance = openingAccountBalance.copy(); - List accountTransactionsSorted = retreiveListOfTransactions(); + final List accountTransactionsSorted = retreiveListOfTransactions(); - for (SavingsAccountTransaction transaction : accountTransactionsSorted) { + for (final SavingsAccountTransaction transaction : accountTransactionsSorted) { if (transaction.isReversed()) { transaction.zeroBalanceFields(); } else { @@ -529,7 +527,7 @@ public class SavingsAccount extends AbstractPersistable { // loop over transactions in reverse LocalDate endOfBalanceDate = DateUtils.getLocalDateOfTenant(); for (int i = accountTransactionsSorted.size() - 1; i >= 0; i--) { - SavingsAccountTransaction transaction = accountTransactionsSorted.get(i); + final SavingsAccountTransaction transaction = accountTransactionsSorted.get(i); if (transaction.isNotReversed()) { transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); @@ -694,8 +692,8 @@ public class SavingsAccount extends AbstractPersistable { boolean transactionBeforeLastInterestPosting = false; - for (SavingsAccountTransaction transaction : retreiveListOfTransactions()) { - if (transaction.isNotReversed() && transaction.isInterestPostingAndNotReversed() && transaction.isAfter(transactionDate)) { + for (final SavingsAccountTransaction transaction : retreiveListOfTransactions()) { + if (transaction.isInterestPostingAndNotReversed() && transaction.isAfter(transactionDate)) { transactionBeforeLastInterestPosting = true; break; } @@ -709,7 +707,7 @@ public class SavingsAccount extends AbstractPersistable { Money runningBalance = Money.zero(this.currency); - for (SavingsAccountTransaction transaction : transactionsSortedByDate) { + for (final SavingsAccountTransaction transaction : transactionsSortedByDate) { if (transaction.isNotReversed() && transaction.isCredit()) { runningBalance = runningBalance.plus(transaction.getAmount(this.currency)); } else if (transaction.isNotReversed() && transaction.isDebit()) { @@ -725,11 +723,12 @@ public class SavingsAccount extends AbstractPersistable { } } - private void validateAccountBalanceDoesNotBecomeNegativeDueToUndoAction(final List transactionsSortedByDate) { + public void validateAccountBalanceDoesNotBecomeNegative(String transactionAction) { + final List transactionsSortedByDate = retreiveListOfTransactions(); Money runningBalance = Money.zero(this.currency); - for (SavingsAccountTransaction transaction : transactionsSortedByDate) { + for (final SavingsAccountTransaction transaction : transactionsSortedByDate) { if (transaction.isNotReversed() && transaction.isCredit()) { runningBalance = runningBalance.plus(transaction.getAmount(this.currency)); } else if (transaction.isNotReversed() && transaction.isDebit()) { @@ -740,7 +739,7 @@ public class SavingsAccount extends AbstractPersistable { // final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.undoTransactionAction); + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + transactionAction); baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.going.negative"); @@ -763,7 +762,7 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - LocalDate nextAnnualFeeDueDate = getNextAnnualFeeDueDate(); + final LocalDate nextAnnualFeeDueDate = getNextAnnualFeeDueDate(); if (nextAnnualFeeDueDate == null || annualFeeSettingsNotSet()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.annualfee.settings"); @@ -802,7 +801,7 @@ public class SavingsAccount extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final Money annualFee = Money.of(this.currency, this.annualFeeAmount); - SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, annualFeeTransactionDate, annualFee); + final SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, annualFeeTransactionDate, annualFee); this.transactions.add(annualFeeTransaction); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); @@ -811,7 +810,7 @@ public class SavingsAccount extends AbstractPersistable { currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate(); if (currentAnnualFeeNextDueDate != null) { - LocalDate newAnnualFeeNextDueDate = new LocalDate(currentAnnualFeeNextDueDate).withMonthOfYear(this.annualFeeOnMonth) + final LocalDate newAnnualFeeNextDueDate = new LocalDate(currentAnnualFeeNextDueDate).withMonthOfYear(this.annualFeeOnMonth) .withDayOfMonth(this.annualFeeOnDay).plusYears(1); this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate(); } else { @@ -843,7 +842,7 @@ public class SavingsAccount extends AbstractPersistable { } private void updateToNextAnnualFeeDueDateFrom(final LocalDate startingDate) { - LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay); + final LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay); this.annualFeeNextDueDate = nextDueLocalDate.toDate(); } @@ -898,7 +897,7 @@ public class SavingsAccount extends AbstractPersistable { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction); - SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state"); @@ -1120,16 +1119,16 @@ public class SavingsAccount extends AbstractPersistable { .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); if (this.lockinPeriodFrequency == null) { - baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).ignoreIfNull() + baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(this.lockinPeriodFrequencyType).ignoreIfNull() .inMinMaxRange(0, 3); if (this.lockinPeriodFrequencyType != null) { - baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull() + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(this.lockinPeriodFrequency).notNull() .integerZeroOrGreater(); } } else { - baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequencyType).integerZeroOrGreater(); - baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull() + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(this.lockinPeriodFrequencyType).integerZeroOrGreater(); + baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(this.lockinPeriodFrequencyType).notNull() .inMinMaxRange(0, 3); } @@ -1140,14 +1139,14 @@ public class SavingsAccount extends AbstractPersistable { final List existingReversedTransactionIds) { final Map accountingBridgeData = new LinkedHashMap(); - accountingBridgeData.put("savingsId", this.getId()); - accountingBridgeData.put("savingsProductId", this.productId()); - accountingBridgeData.put("officeId", this.officeId()); - accountingBridgeData.put("cashBasedAccountingEnabled", this.isCashBasedAccountingEnabledOnSavingsProduct()); - accountingBridgeData.put("accrualBasedAccountingEnabled", this.isAccrualBasedAccountingEnabledOnSavingsProduct()); + accountingBridgeData.put("savingsId", getId()); + accountingBridgeData.put("savingsProductId", productId()); + accountingBridgeData.put("officeId", officeId()); + accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnSavingsProduct()); + accountingBridgeData.put("accrualBasedAccountingEnabled", isAccrualBasedAccountingEnabledOnSavingsProduct()); final List> newLoanTransactions = new ArrayList>(); - for (SavingsAccountTransaction transaction : this.transactions) { + for (final SavingsAccountTransaction transaction : this.transactions) { if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) { newLoanTransactions.add(transaction.toMapData(currencyData)); } else if (!existingTransactionIds.contains(transaction.getId())) { @@ -1161,9 +1160,9 @@ public class SavingsAccount extends AbstractPersistable { private Collection findExistingTransactionIds() { - Collection ids = new ArrayList(); + final Collection ids = new ArrayList(); - for (SavingsAccountTransaction transaction : this.transactions) { + for (final SavingsAccountTransaction transaction : this.transactions) { ids.add(transaction.getId()); } @@ -1174,7 +1173,7 @@ public class SavingsAccount extends AbstractPersistable { final Collection ids = new ArrayList(); - for (SavingsAccountTransaction transaction : this.transactions) { + for (final SavingsAccountTransaction transaction : this.transactions) { if (transaction.isReversed()) { ids.add(transaction.getId()); } @@ -1275,12 +1274,12 @@ public class SavingsAccount extends AbstractPersistable { .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); } - if (client != null && client.isActivatedAfter(submittedOn)) { - baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(client.getActivationLocalDate()) + if (this.client != null && this.client.isActivatedAfter(submittedOn)) { + baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(this.client.getActivationLocalDate()) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); - } else if (group != null && group.isActivatedAfter(submittedOn)) { + } else if (this.group != null && this.group.isActivatedAfter(submittedOn)) { - baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(group.getActivationLocalDate()) + baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(this.group.getActivationLocalDate()) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); } @@ -1315,7 +1314,7 @@ public class SavingsAccount extends AbstractPersistable { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.approvalAction); - SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName) .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state"); @@ -1327,8 +1326,8 @@ public class SavingsAccount extends AbstractPersistable { actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); // only do below if status has changed in the 'approval' case - LocalDate approvedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName); - String approvedOnDateChange = command.stringValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName); + final LocalDate approvedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName); + final String approvedOnDateChange = command.stringValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName); this.approvedOnDate = approvedOn.toDate(); this.approvedBy = currentUser; @@ -1374,7 +1373,7 @@ public class SavingsAccount extends AbstractPersistable { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.undoApprovalAction); - SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) { baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName) @@ -1402,11 +1401,10 @@ public class SavingsAccount extends AbstractPersistable { return actualChanges; } - public void undoTransaction(final Long transactionId, final List reversedTransactionIds, final MathContext mc, - final LocalDate upToInterestCalculationDate) { + public void undoTransaction(final Long transactionId, final List reversedTransactionIds) { SavingsAccountTransaction transactionToUndo = null; - for (SavingsAccountTransaction transaction : this.transactions) { + for (final SavingsAccountTransaction transaction : this.transactions) { if (transaction.isIdentifiedBy(transactionId)) { transactionToUndo = transaction; } @@ -1424,15 +1422,12 @@ public class SavingsAccount extends AbstractPersistable { if (this.annualFeeNextDueDate == null) { updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); } else { - LocalDate newAnnualFeeNextDueDate = new LocalDate(this.annualFeeNextDueDate).withMonthOfYear(this.annualFeeOnMonth) + final LocalDate newAnnualFeeNextDueDate = new LocalDate(this.annualFeeNextDueDate).withMonthOfYear(this.annualFeeOnMonth) .withDayOfMonth(this.annualFeeOnDay).plusYears(1); this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate(); } } - calculateInterestUsing(mc, upToInterestCalculationDate); - - validateAccountBalanceDoesNotBecomeNegativeDueToUndoAction(retreiveListOfTransactions()); } } @@ -1441,7 +1436,7 @@ public class SavingsAccount extends AbstractPersistable { Date nextDueDate = null; LocalDate lastAnnualFeeTransactionDate = null; - for (SavingsAccountTransaction transaction : retreiveOrderedListOfTransactions()) { + for (final SavingsAccountTransaction transaction : retreiveOrderedListOfTransactions()) { if (transaction.isAnnualFeeAndNotReversed()) { if (lastAnnualFeeTransactionDate == null) { lastAnnualFeeTransactionDate = transaction.transactionLocalDate(); @@ -1466,7 +1461,7 @@ public class SavingsAccount extends AbstractPersistable { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.rejectAction); - SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName) @@ -1478,7 +1473,7 @@ public class SavingsAccount extends AbstractPersistable { this.status = SavingsAccountStatusType.REJECTED.getValue(); actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); - LocalDate rejectedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName); + final LocalDate rejectedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName); final String rejectedOnAsString = command.stringValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName); this.rejectedOnDate = rejectedOn.toDate(); @@ -1524,7 +1519,7 @@ public class SavingsAccount extends AbstractPersistable { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.withdrawnByApplicantAction); - SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName) @@ -1583,7 +1578,7 @@ public class SavingsAccount extends AbstractPersistable { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.activateAction); - SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) { baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName) @@ -1616,14 +1611,19 @@ public class SavingsAccount extends AbstractPersistable { updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); } - if (this.client.isActivatedAfter(activationDate)) { - + if (this.client != null && this.client.isActivatedAfter(activationDate)) { final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); - final String dateAsString = formatter.print(client.getActivationLocalDate()); - + final String dateAsString = formatter.print(this.client.getActivationLocalDate()); baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString) .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + if (this.group != null && this.group.isActivatedAfter(activationDate)) { + final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); + final String dateAsString = formatter.print(this.client.getActivationLocalDate()); + baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.group.activation.date"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index 9468df5be..bf58caf9b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -34,11 +34,13 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper; +import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; +import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException; import org.mifosplatform.portfolio.group.exception.CenterNotActiveException; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; @@ -69,8 +71,7 @@ public class SavingsAccountAssembler { public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository, final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository, - final SavingsAccountRepositoryWrapper savingsAccountRepository, - final FromJsonHelper fromApiJsonHelper) { + final SavingsAccountRepositoryWrapper savingsAccountRepository, final FromJsonHelper fromApiJsonHelper) { this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.clientRepository = clientRepository; this.groupRepository = groupRepository; @@ -99,21 +100,27 @@ public class SavingsAccountAssembler { Client client = null; Group group = null; Staff fieldOfficer = null; - + AccountType accountType = AccountType.INVALID; final Long clientId = fromApiJsonHelper.extractLongNamed(clientIdParamName, element); if (clientId != null) { client = this.clientRepository.findOneWithNotFoundDetection(clientId); + accountType = AccountType.INDIVIDUAL; if (client.isNotActive()) { throw new ClientNotActiveException(clientId); } } final Long groupId = fromApiJsonHelper.extractLongNamed(groupIdParamName, element); if (groupId != null) { group = this.groupRepository.findOneWithNotFoundDetection(groupId); + accountType = AccountType.GROUP; + } + + if (group != null && client != null) { + if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); } + accountType = AccountType.JLG; if (group.isNotActive()) { if (group.isCenter()) { throw new CenterNotActiveException(groupId); } throw new GroupNotActiveException(groupId); } - } final Long fieldOfficerId = fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element); @@ -219,10 +226,10 @@ public class SavingsAccountAssembler { monthDayOfAnnualFee = product.monthDayOfAnnualFee(); } - final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, externalId, - submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, - interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, monthDayOfAnnualFee); + final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, + externalId, accountType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, monthDayOfAnnualFee); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant()); @@ -236,4 +243,8 @@ public class SavingsAccountAssembler { return account; } + + public void assignSavingAccountHelpers(SavingsAccount savingsAccount){ + savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainService.java new file mode 100644 index 000000000..96ddfa376 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainService.java @@ -0,0 +1,21 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; + +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormatter; +import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; + +public interface SavingsAccountDomainService { + + SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, + BigDecimal transactionAmount, PaymentDetail paymentDetail); + + SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, + BigDecimal transactionAmount, PaymentDetail paymentDetail); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java new file mode 100644 index 000000000..286a28125 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -0,0 +1,117 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormatter; +import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService { + + private final SavingsAccountRepositoryWrapper savingsAccountRepository; + private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; + private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; + private final JournalEntryWritePlatformService journalEntryWritePlatformService; + + @Autowired + public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository, + final SavingsAccountTransactionRepository savingsAccountTransactionRepository, + final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, + final JournalEntryWritePlatformService journalEntryWritePlatformService) { + this.savingsAccountRepository = savingsAccountRepository; + this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; + this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; + } + + @Transactional + @Override + public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) { + + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + final SavingsAccountTransaction withdrawal = account.withdraw(fmt, transactionDate, transactionAmount, existingTransactionIds, + existingReversedTransactionIds, paymentDetail); + + final MathContext mc = MathContext.DECIMAL64; + if (account.isBeforeLastPostingPeriod(transactionDate)) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.calculateInterestUsing(mc, today); + } + + saveTransactionToGenerateTransactionId(withdrawal); + this.savingsAccountRepository.save(account); + + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + + return withdrawal; + } + + @Transactional + @Override + public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) { + + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + final SavingsAccountTransaction deposit = account.deposit(fmt, transactionDate, transactionAmount, existingTransactionIds, + existingReversedTransactionIds, paymentDetail); + + final MathContext mc = MathContext.DECIMAL64; + if (account.isBeforeLastPostingPeriod(transactionDate)) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.calculateInterestUsing(mc, today); + } + + saveTransactionToGenerateTransactionId(deposit); + + this.savingsAccountRepository.save(account); + + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + + return deposit; + } + + private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) { + this.savingsAccountTransactionRepository.save(transaction); + return transaction.getId(); + } + + private void postJournalEntries(final SavingsAccount savingsAccount, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + final MonetaryCurrency currency = savingsAccount.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); + + final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), + existingTransactionIds, existingReversedTransactionIds); + journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepository.java index c5f3940b8..69c8e3b37 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepository.java @@ -16,6 +16,10 @@ public interface SavingsAccountRepository extends JpaRepository findSavingAccountByClientId(@Param("clientId") Long clientId); + + @Query("from SavingsAccount s_acc where s_acc.status = :status") + List findSavingAccountByStatus(@Param("status") Integer status); + @Query("from SavingsAccount sa where sa.client.id = :clientId and sa.group.id = :groupId") List findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java index c04174883..986cee5fd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java @@ -28,7 +28,6 @@ public final class SavingsAccountSummary { @Column(name = "total_withdrawals_derived", scale = 6, precision = 19) private BigDecimal totalWithdrawals; - @SuppressWarnings("unused") @Column(name = "total_interest_earned_derived", scale = 6, precision = 19) private BigDecimal totalInterestEarned; @@ -65,7 +64,7 @@ public final class SavingsAccountSummary { Money totalEarned = Money.zero(currency); - for (PostingPeriod period : allPostingPeriods) { + for (final PostingPeriod period : allPostingPeriods) { totalEarned = totalEarned.plus(period.interest()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index e9a27c633..edae019c0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -38,7 +38,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_savings_account_transaction") public final class SavingsAccountTransaction extends AbstractPersistable { - @SuppressWarnings("unused") @ManyToOne(optional = false) @JoinColumn(name = "savings_account_id", nullable = false) private SavingsAccount savingsAccount; @@ -63,7 +62,6 @@ public final class SavingsAccountTransaction extends AbstractPersistable { @Column(name = "running_balance_derived", scale = 6, precision = 19, nullable = false) private BigDecimal runningBalance; - @SuppressWarnings("unused") @Column(name = "cumulative_balance_derived", scale = 6, precision = 19, nullable = false) private BigDecimal cumulativeBalance; @@ -221,10 +219,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable { final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(this.typeOf); - thisTransactionData.put("id", this.getId()); + thisTransactionData.put("id", getId()); thisTransactionData.put("type", transactionType); - thisTransactionData.put("reversed", Boolean.valueOf(this.isReversed())); - thisTransactionData.put("date", this.getTransactionLocalDate()); + thisTransactionData.put("reversed", Boolean.valueOf(isReversed())); + thisTransactionData.put("date", getTransactionLocalDate()); thisTransactionData.put("currency", currencyData); thisTransactionData.put("amount", this.amount); @@ -236,20 +234,20 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public boolean isAfter(final LocalDate transactionDate) { - return this.getTransactionLocalDate().isAfter(transactionDate); + return getTransactionLocalDate().isAfter(transactionDate); } public EndOfDayBalance toEndOfDayBalance(final LocalDateInterval periodInterval, final MonetaryCurrency currency) { - Money endOfDayBalance = Money.of(currency, this.runningBalance); - Money openingBalance = endOfDayBalance; + final Money endOfDayBalance = Money.of(currency, this.runningBalance); + final Money openingBalance = endOfDayBalance; LocalDate balanceDate = periodInterval.startDate(); int numberOfDays = periodInterval.daysInPeriodInclusiveOfEndDate(); if (periodInterval.contains(getTransactionLocalDate())) { balanceDate = getTransactionLocalDate(); - LocalDateInterval newInterval = LocalDateInterval.create(getTransactionLocalDate(), periodInterval.endDate()); + final LocalDateInterval newInterval = LocalDateInterval.create(getTransactionLocalDate(), periodInterval.endDate()); numberOfDays = newInterval.daysInPeriodInclusiveOfEndDate(); } @@ -258,7 +256,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { public EndOfDayBalance toEndOfDayBalance(final Money openingBalance, final LocalDate nextTransactionDate) { - MonetaryCurrency currency = openingBalance.getCurrency(); + final MonetaryCurrency currency = openingBalance.getCurrency(); Money endOfDayBalance = openingBalance.copy(); if (isDeposit()) { endOfDayBalance = openingBalance.plus(getAmount(currency)); @@ -274,12 +272,15 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public EndOfDayBalance toEndOfDayBalance(final Money openingBalance) { - MonetaryCurrency currency = openingBalance.getCurrency(); + final MonetaryCurrency currency = openingBalance.getCurrency(); Money endOfDayBalance = openingBalance.copy(); if (isDeposit()) { endOfDayBalance = openingBalance.plus(getAmount(currency)); } else if (isWithdrawal() || isWithdrawalFeeAndNotReversed()) { - endOfDayBalance = openingBalance.minus(getAmount(currency)); + + if (openingBalance.isGreaterThanZero()) { + endOfDayBalance = openingBalance.minus(getAmount(currency)); + } } return EndOfDayBalance.from(getTransactionLocalDate(), openingBalance, endOfDayBalance, this.balanceNumberOfDays); @@ -287,7 +288,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, final LocalDateInterval boundedBy) { - MonetaryCurrency currency = openingBalance.getCurrency(); + final MonetaryCurrency currency = openingBalance.getCurrency(); Money endOfDayBalance = openingBalance.copy(); int numberOfDaysOfBalance = this.balanceNumberOfDays; @@ -297,7 +298,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { if (boundedBy.startDate().isAfter(balanceStartDate)) { balanceStartDate = boundedBy.startDate(); - LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); + final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); } else { if (isDeposit()) { @@ -309,7 +310,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { if (balanceEndDate.isAfter(boundedBy.endDate())) { balanceEndDate = boundedBy.endDate(); - LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); + final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); } @@ -321,17 +322,17 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public boolean fallsWithin(final LocalDateInterval periodInterval) { - LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); + final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); return periodInterval.contains(balanceInterval); } public boolean spansAnyPortionOf(final LocalDateInterval periodInterval) { - LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); + final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); return balanceInterval.containsPortionOf(periodInterval); } public boolean isIdentifiedBy(final Long transactionId) { - return this.getId().equals(transactionId); + return getId().equals(transactionId); } public boolean isCredit() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java index 68eaaae63..80b2f691e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java @@ -12,6 +12,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeO import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; @@ -279,7 +280,7 @@ public class SavingsProduct extends AbstractPersistable { actualChanges.put(digitsAfterDecimalParamName, newValue); actualChanges.put(localeParamName, localeAsInput); digitsAfterDecimal = newValue; - this.currency = new MonetaryCurrency(this.currency.getCode(), digitsAfterDecimal); + this.currency = new MonetaryCurrency(this.currency.getCode(), digitsAfterDecimal,this.currency.getCurrencyInMultiplesOf()); } String currencyCode = this.currency.getCode(); @@ -287,7 +288,16 @@ public class SavingsProduct extends AbstractPersistable { final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName); actualChanges.put(currencyCodeParamName, newValue); currencyCode = newValue; - this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal()); + this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal(),this.currency.getCurrencyInMultiplesOf()); + } + + Integer inMultiplesOf = this.currency.getCurrencyInMultiplesOf(); + if (command.isChangeInIntegerParameterNamed(inMultiplesOfParamName, inMultiplesOf)) { + final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName); + actualChanges.put(inMultiplesOfParamName, newValue); + actualChanges.put(localeParamName, localeAsInput); + inMultiplesOf = newValue; + this.currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDigitsAfterDecimal(),inMultiplesOf); } if (command.isChangeInBigDecimalParameterNamed(nominalAnnualInterestRateParamName, this.nominalAnnualInterestRate)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java index 0a01047c2..d881b5a32 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java @@ -10,6 +10,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeO import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; @@ -46,7 +47,8 @@ public class SavingsProductAssembler { final String currencyCode = command.stringValueOfParameterNamed(currencyCodeParamName); final Integer digitsAfterDecimal = command.integerValueOfParameterNamed(digitsAfterDecimalParamName); - MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal); + final Integer inMultiplesOf = command.integerValueOfParameterNamed(inMultiplesOfParamName); + MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal,inMultiplesOf); final BigDecimal interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/SavingsAccountTransactionNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/SavingsAccountTransactionNotFoundException.java new file mode 100755 index 000000000..10ac75ad7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/SavingsAccountTransactionNotFoundException.java @@ -0,0 +1,12 @@ +package org.mifosplatform.portfolio.savings.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class SavingsAccountTransactionNotFoundException extends AbstractPlatformResourceNotFoundException { + + public SavingsAccountTransactionNotFoundException(final Long savingsId, final Long transactionId) { + super("error.msg.saving.account.trasaction.id.invalid", "Savings account with savings identifier " + savingsId + + " and trasaction identifier " + transactionId + " does not exist", savingsId, transactionId); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/TransactionUpdateNotAllowedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/TransactionUpdateNotAllowedException.java new file mode 100755 index 000000000..7635aa184 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/TransactionUpdateNotAllowedException.java @@ -0,0 +1,15 @@ +package org.mifosplatform.portfolio.savings.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformServiceUnavailableException; + +/** + * A {@link RuntimeException} thrown when update not allowed. + */ +public class TransactionUpdateNotAllowedException extends AbstractPlatformServiceUnavailableException { + + public TransactionUpdateNotAllowedException(final Long savingsId, final Long transactionId) { + super("error.msg.saving.account.trasaction.update.notallowed", + "Savings Account transaction update not allowed with savings identifier " + savingsId + " and trasaction identifier " + + transactionId, savingsId, transactionId); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsTransactionAdjustmentCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsTransactionAdjustmentCommandHandler.java new file mode 100755 index 000000000..f6571df9e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsTransactionAdjustmentCommandHandler.java @@ -0,0 +1,28 @@ +package org.mifosplatform.portfolio.savings.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SavingsTransactionAdjustmentCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public SavingsTransactionAdjustmentCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + final Long transactionId = Long.valueOf(command.getTransactionId()); + return this.writePlatformService.adjustSavingsTransaction(command.getSavingsId(), transactionId, command); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java index 677d97f4b..f09125c94 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java @@ -17,6 +17,8 @@ public interface SavingsAccountReadPlatformService { Page retrieveAll(SearchParameters searchParameters); + Collection retrieveAllForLookup(Long clientId); + SavingsAccountData retrieveOne(Long savingsId); SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long productId, boolean staffInSelectedOfficeOnly); @@ -26,4 +28,6 @@ public interface SavingsAccountReadPlatformService { Collection retrieveAllTransactions(Long savingsId); Collection retrieveAccountsWithAnnualFeeDue(); + + SavingsAccountTransactionData retrieveSavingsTransaction(Long savingsId, Long transactionId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index c71b96f72..fcbe61f4b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -26,6 +26,7 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.organisation.staff.service.StaffReadPlatformService; +import org.mifosplatform.portfolio.account.data.AccountTransferData; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; @@ -74,7 +75,6 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead // pagination private final PaginationHelper paginationHelper = new PaginationHelper(); - @Autowired public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService, @@ -93,6 +93,16 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead this.annualFeeMapper = new SavingsAccountAnnualFeeMapper(); } + @Override + public Collection retrieveAllForLookup(final Long clientId) { + + StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema()); + sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300"); + + Object[] queryParameters = new Object[] { clientId }; + return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); + } + @Override public Page retrieveAll(final SearchParameters searchParameters) { @@ -148,12 +158,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead public SavingsAccountData retrieveOne(final Long accountId) { try { - this.context.authenticatedUser(); + final String sql = "select " + this.savingAccountMapper.schema() + " where sa.id = ?"; - final SavingAccountMapper mapper = new SavingAccountMapper(); - final String sql = "select " + mapper.schema() + " where sa.id = ?"; - - return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { accountId }); + return this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, new Object[] { accountId }); } catch (EmptyResultDataAccessException e) { throw new SavingsAccountNotFoundException(accountId); } @@ -196,7 +203,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("cbu.username as closedByUsername,"); sqlBuilder.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); - sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, "); + sqlBuilder + .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); @@ -304,8 +312,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate"); @@ -500,10 +509,18 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId }); } + @Override + public SavingsAccountTransactionData retrieveSavingsTransaction(final Long savingsId, final Long transactionId) { + + final String sql = "select " + this.transactionsMapper.schema() + " where sa.id = ? and tr.id= ?"; + + return this.jdbcTemplate.queryForObject(sql, this.transactionsMapper, new Object[] { savingsId, transactionId }); + } + @Override public Collection retrieveAccountsWithAnnualFeeDue() { final String sql = "select " + annualFeeMapper.schema() - + " where sa.annual_fee_next_due_date not null and sa.annual_fee_next_due_date <= NOW()"; + + " where sa.annual_fee_next_due_date is not null and sa.annual_fee_next_due_date <= NOW()"; return this.jdbcTemplate.query(sql, this.annualFeeMapper, new Object[] {}); } @@ -518,16 +535,25 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, "); sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,"); sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,"); + sqlBuilder.append("fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,"); + sqlBuilder.append("fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,"); + sqlBuilder.append("fromtran.description as fromTransferDescription,"); + sqlBuilder.append("totran.id as toTransferId, totran.is_reversed as toTransferReversed,"); + sqlBuilder.append("totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,"); + sqlBuilder.append("totran.description as toTransferDescription,"); sqlBuilder.append("sa.id as savingsId, sa.account_no as accountNo,"); sqlBuilder.append("pd.payment_type_cv_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "); sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "); - sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, "); + sqlBuilder + .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); sqlBuilder.append("cv.code_value as paymentTypeName "); sqlBuilder.append("from m_savings_account sa "); sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id "); sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + sqlBuilder.append("left join m_savings_account_transfer fromtran on fromtran.from_savings_transaction_id = tr.id "); + sqlBuilder.append("left join m_savings_account_transfer totran on totran.to_savings_transaction_id = tr.id "); sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id "); sqlBuilder.append("left join m_code_value cv on pd.payment_type_cv_id = cv.id "); @@ -573,11 +599,33 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); + + AccountTransferData transfer = null; + final Long fromTransferId = JdbcSupport.getLong(rs, "fromTransferId"); + final Long toTransferId = JdbcSupport.getLong(rs, "toTransferId"); + if (fromTransferId != null) { + final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, "fromTransferDate"); + final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fromTransferAmount"); + final boolean fromTransferReversed = rs.getBoolean("fromTransferReversed"); + final String fromTransferDescription = rs.getString("fromTransferDescription"); + + transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate, + fromTransferDescription, fromTransferReversed); + } else if (toTransferId != null) { + final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, "toTransferDate"); + final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "toTransferAmount"); + final boolean toTransferReversed = rs.getBoolean("toTransferReversed"); + final String toTransferDescription = rs.getString("toTransferDescription"); + + transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate, + toTransferDescription, toTransferReversed); + } return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, - amount, runningBalance, reversed); + amount, runningBalance, reversed, transfer); } } @@ -588,7 +636,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead public SavingsAccountTransactionTemplateMapper() { final StringBuilder sqlBuilder = new StringBuilder(400); sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); - sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, "); + sqlBuilder + .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance "); @@ -613,8 +662,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); return SavingsAccountTransactionData.template(savingsId, accountNo, DateUtils.getLocalDateOfTenant(), currency); } @@ -633,7 +683,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final StringBuilder sqlBuilder = new StringBuilder(400); sqlBuilder.append("sp.id as productId, sp.name as productName, "); - sqlBuilder.append("sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, "); + sqlBuilder + .append("sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, sp.currency_multiplesof as inMultiplesOf, "); sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); sqlBuilder.append("sp.nominal_annual_interest_rate as nominalAnnualIterestRate, "); @@ -670,8 +721,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); final BigDecimal nominalAnnualIterestRate = rs.getBigDecimal("nominalAnnualIterestRate"); @@ -753,7 +805,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead public SavingsAccountAnnualFeeMapper() { final StringBuilder sqlBuilder = new StringBuilder(200); sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); - sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate, "); + sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate "); sqlBuilder.append("from m_savings_account sa "); this.schemaSql = sqlBuilder.toString(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java index 42ffbcc9b..283c358ab 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -24,4 +24,8 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult postInterest(Long savingsId); CommandProcessingResult undoTransaction(Long savingsId, Long transactionId); + + void postInterestForAccounts(); + + CommandProcessingResult adjustSavingsTransaction(Long savingsId, Long transactionId, JsonCommand command); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index b66ace3af..1bc9b4562 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -22,6 +22,8 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.infrastructure.jobs.annotation.CronTarget; +import org.mifosplatform.infrastructure.jobs.service.JobName; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; @@ -32,12 +34,17 @@ import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; +import org.mifosplatform.portfolio.savings.SavingsApiConstants; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDataValidator; import org.mifosplatform.portfolio.savings.domain.SavingsAccount; import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler; -import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepositoryWrapper; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountDomainService; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionRepository; +import org.mifosplatform.portfolio.savings.exception.SavingsAccountTransactionNotFoundException; +import org.mifosplatform.portfolio.savings.exception.TransactionUpdateNotAllowedException; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -47,23 +54,25 @@ import org.springframework.transaction.annotation.Transactional; public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements SavingsAccountWritePlatformService { private final PlatformSecurityContext context; - private final SavingsAccountRepositoryWrapper savingAccountRepository; + private final SavingsAccountRepository savingAccountRepository; private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; private final SavingsAccountAssembler savingAccountAssembler; private final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final JournalEntryWritePlatformService journalEntryWritePlatformService; + private final SavingsAccountDomainService savingsAccountDomainService; @Autowired public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final SavingsAccountRepositoryWrapper savingAccountRepository, + final SavingsAccountRepository savingAccountRepository, final SavingsAccountTransactionRepository savingsAccountTransactionRepository, final SavingsAccountAssembler savingAccountAssembler, final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator, final PaymentDetailWritePlatformService paymentDetailWritePlatformService, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, - final JournalEntryWritePlatformService journalEntryWritePlatformService) { + final JournalEntryWritePlatformService journalEntryWritePlatformService, + final SavingsAccountDomainService savingsAccountDomainService) { this.context = context; this.savingAccountRepository = savingAccountRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; @@ -72,6 +81,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; + this.savingsAccountDomainService = savingsAccountDomainService; } @Transactional @@ -123,36 +133,19 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); final Map changes = new LinkedHashMap(); PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); - final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - final SavingsAccountTransaction deposit = account.deposit(fmt, transactionDate, transactionAmount, existingTransactionIds, - existingReversedTransactionIds, paymentDetail); - - if (account.isBeforeLastPostingPeriod(transactionDate)) { - final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); - } else { - final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.calculateInterestUsing(mc, today); - } - - final Long transactionId = saveTransactionToGenerateTransactionId(deposit); - - this.savingAccountRepository.save(account); - - postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, + transactionAmount, paymentDetail); return new CommandProcessingResultBuilder() // - .withEntityId(transactionId) // + .withEntityId(deposit.getId()) // .withOfficeId(account.officeId()) // .withClientId(account.clientId()) // .withGroupId(account.groupId()) // .withSavingsId(savingsId) // - .with(changes)// + .with(changes) // .build(); } @@ -165,43 +158,25 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult withdrawal(final Long savingsId, final JsonCommand command) { - this.context.authenticatedUser(); - this.savingsAccountTransactionDataValidator.validate(command); - final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); - checkClientOrGroupActive(account); - final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); final Map changes = new LinkedHashMap(); PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); - final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - final SavingsAccountTransaction withdrawal = account.withdraw(fmt, transactionDate, transactionAmount, existingTransactionIds, - existingReversedTransactionIds, paymentDetail); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + checkClientOrGroupActive(account); - if (account.isBeforeLastPostingPeriod(transactionDate)) { - final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); - } else { - final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.calculateInterestUsing(mc, today); - } - - final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal); - this.savingAccountRepository.save(account); - - postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate, + transactionAmount, paymentDetail); return new CommandProcessingResultBuilder() // - .withEntityId(transactionId) // + .withEntityId(withdrawal.getId()) // .withOfficeId(account.officeId()) // .withClientId(account.clientId()) // .withGroupId(account.groupId()) // @@ -268,12 +243,22 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult postInterest(final Long savingsId) { - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); - final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); checkClientOrGroupActive(account); + postInterest(account); + return new CommandProcessingResultBuilder() // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .build(); + } + @Transactional + private void postInterest(final SavingsAccount account) { + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); @@ -281,6 +266,44 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepository.save(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + } + + @CronTarget(jobName = JobName.POST_INTEREST_FOR_SAVINGS) + @Override + public void postInterestForAccounts() { + final List savingsAccounts = this.savingAccountRepository.findSavingAccountByStatus(SavingsAccountStatusType.ACTIVE + .getValue()); + for (final SavingsAccount savingsAccount : savingsAccounts) { + this.savingAccountAssembler.assignSavingAccountHelpers(savingsAccount); + postInterest(savingsAccount); + } + } + + @Override + public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId) { + + final List newTransactionIds = new ArrayList(); + final List reversedTransactionIds = new ArrayList(); + + SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository.findOneByIdAndSavingsAccountId( + transactionId, savingsId); + if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); } + + final LocalDate today = DateUtils.getLocalDateOfTenant(); + final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN); + + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + account.undoTransaction(transactionId, reversedTransactionIds); + checkClientOrGroupActive(account); + if ((savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal()) + && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { + account.postInterest(mc, today, newTransactionIds, reversedTransactionIds); + } else { + account.calculateInterestUsing(mc, today); + } + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction); + + postJournalEntries(account, newTransactionIds, reversedTransactionIds); return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // @@ -292,32 +315,56 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } @Override - public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId) { - - final List newTransactionIds = new ArrayList(); - final List reversedTransactionIds = new ArrayList(); + public CommandProcessingResult adjustSavingsTransaction(final Long savingsId, final Long transactionId, final JsonCommand command) { SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository.findOneByIdAndSavingsAccountId( transactionId, savingsId); - if (savingsAccountTransaction == null) { - // throw new not found exception - } + if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); } + + if (!(savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal()) || savingsAccountTransaction.isReversed()) { throw new TransactionUpdateNotAllowedException( + savingsId, transactionId); } + this.savingsAccountTransactionDataValidator.validate(command); final LocalDate today = DateUtils.getLocalDateOfTenant(); - final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); - account.undoTransaction(transactionId, reversedTransactionIds, mc, today); - checkClientOrGroupActive(account); - postJournalEntries(account, newTransactionIds, reversedTransactionIds); + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate transactionDate = command.localDateValueOfParameterNamed(SavingsApiConstants.transactionDateParamName); + final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.transactionAmountParamName); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + final Map changes = new LinkedHashMap(); + PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); + account.undoTransaction(transactionId, existingReversedTransactionIds); + SavingsAccountTransaction transaction = null; + if (savingsAccountTransaction.isDeposit()) { + transaction = account.deposit(fmt, transactionDate, transactionAmount, existingTransactionIds, existingReversedTransactionIds, + paymentDetail); + } else { + transaction = account.withdraw(fmt, transactionDate, transactionAmount, existingTransactionIds, existingReversedTransactionIds, + paymentDetail); + } + final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); + + if (account.isBeforeLastPostingPeriod(transactionDate) + || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { + account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + account.calculateInterestUsing(mc, today); + } + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction); + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); return new CommandProcessingResultBuilder() // - .withEntityId(savingsId) // + .withEntityId(newtransactionId) // .withOfficeId(account.officeId()) // .withClientId(account.clientId()) // .withGroupId(account.groupId()) // .withSavingsId(savingsId) // + .with(changes)// .build(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index 9f439c3c5..acde05eb2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -151,14 +151,14 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final Long savingsId = account.getId(); return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(savingsId) // - .withOfficeId(account.officeId()) // - .withClientId(account.clientId()) // - .withGroupId(account.groupId()) // - .withSavingsId(savingsId) // - .build(); - } catch (DataAccessException dve) { + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .build(); + } catch (final DataAccessException dve) { handleDataIntegrityIssues(command, dve); return CommandProcessingResult.empty(); } @@ -230,15 +230,15 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl } return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(savingsId) // - .withOfficeId(account.officeId()) // - .withClientId(account.clientId()) // - .withGroupId(account.groupId()) // - .withSavingsId(savingsId) // - .with(changes) // - .build(); - } catch (DataAccessException dve) { + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); + } catch (final DataAccessException dve) { handleDataIntegrityIssues(command, dve); return new CommandProcessingResult(Long.valueOf(-1)); } @@ -254,33 +254,33 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl if (account.isNotSubmittedAndPendingApproval()) { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.deleteApplicationAction); + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.deleteApplicationAction); baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName) - .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state"); + .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - List relatedNotes = this.noteRepository.findBySavingsAccountId(savingsId); + final List relatedNotes = this.noteRepository.findBySavingsAccountId(savingsId); this.noteRepository.deleteInBatch(relatedNotes); this.savingAccountRepository.delete(account); return new CommandProcessingResultBuilder() // - .withEntityId(savingsId) // - .withOfficeId(account.officeId()) // - .withClientId(account.clientId()) // - .withGroupId(account.groupId()) // - .withSavingsId(savingsId) // - .build(); + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .build(); } @Transactional @Override public CommandProcessingResult approveApplication(final Long savingsId, final JsonCommand command) { - final AppUser currentUser = context.authenticatedUser(); + final AppUser currentUser = this.context.authenticatedUser(); this.savingsAccountApplicationTransitionApiJsonValidator.validateApproval(command.json()); @@ -293,28 +293,28 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { - Note note = Note.savingNote(savingsAccount, noteText); + final Note note = Note.savingNote(savingsAccount, noteText); changes.put("note", noteText); this.noteRepository.save(note); } } return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(savingsId) // - .withOfficeId(savingsAccount.officeId()) // - .withClientId(savingsAccount.clientId()) // - .withGroupId(savingsAccount.groupId()) // - .withSavingsId(savingsId) // - .with(changes) // - .build(); + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); } @Transactional @Override public CommandProcessingResult undoApplicationApproval(final Long savingsId, final JsonCommand command) { - context.authenticatedUser(); + this.context.authenticatedUser(); this.savingsAccountApplicationTransitionApiJsonValidator.validateForUndo(command.json()); @@ -327,28 +327,28 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { - Note note = Note.savingNote(savingsAccount, noteText); + final Note note = Note.savingNote(savingsAccount, noteText); changes.put("note", noteText); this.noteRepository.save(note); } } return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(savingsId) // - .withOfficeId(savingsAccount.officeId()) // - .withClientId(savingsAccount.clientId()) // - .withGroupId(savingsAccount.groupId()) // - .withSavingsId(savingsId) // - .with(changes) // - .build(); + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); } @Transactional @Override public CommandProcessingResult rejectApplication(final Long savingsId, final JsonCommand command) { - final AppUser currentUser = context.authenticatedUser(); + final AppUser currentUser = this.context.authenticatedUser(); this.savingsAccountApplicationTransitionApiJsonValidator.validateRejection(command.json()); @@ -361,27 +361,27 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { - Note note = Note.savingNote(savingsAccount, noteText); + final Note note = Note.savingNote(savingsAccount, noteText); changes.put("note", noteText); this.noteRepository.save(note); } } return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(savingsId) // - .withOfficeId(savingsAccount.officeId()) // - .withClientId(savingsAccount.clientId()) // - .withGroupId(savingsAccount.groupId()) // - .withSavingsId(savingsId) // - .with(changes) // - .build(); + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); } @Transactional @Override public CommandProcessingResult applicantWithdrawsFromApplication(final Long savingsId, final JsonCommand command) { - final AppUser currentUser = context.authenticatedUser(); + final AppUser currentUser = this.context.authenticatedUser(); this.savingsAccountApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json()); @@ -395,21 +395,21 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { - Note note = Note.savingNote(savingsAccount, noteText); + final Note note = Note.savingNote(savingsAccount, noteText); changes.put("note", noteText); this.noteRepository.save(note); } } return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(savingsId) // - .withOfficeId(savingsAccount.officeId()) // - .withClientId(savingsAccount.clientId()) // - .withGroupId(savingsAccount.groupId()) // - .withSavingsId(savingsId) // - .with(changes) // - .build(); + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); } private void postJournalEntries(final SavingsAccount savingsAccount, final List existingTransactionIds, @@ -420,7 +420,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), existingTransactionIds, existingReversedTransactionIds); - journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); + this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); } private void checkClientOrGroupActive(final SavingsAccount account) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java index a66c18f53..6cd36970b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java @@ -75,7 +75,8 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead public SavingProductMapper() { final StringBuilder sqlBuilder = new StringBuilder(400); sqlBuilder.append("sp.id as id, sp.name as name, sp.description as description, "); - sqlBuilder.append("sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, "); + sqlBuilder + .append("sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, sp.currency_multiplesof as inMultiplesOf, "); sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); sqlBuilder.append("sp.nominal_annual_interest_rate as nominalAnnualInterestRate, "); @@ -114,9 +115,9 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead String currencyNameCode = rs.getString("currencyNameCode"); String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, - currencyNameCode); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate"); final Integer compoundingInterestPeriodTypeValue = JdbcSupport.getInteger(rs, "compoundingInterestPeriodType"); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java new file mode 100644 index 000000000..4920de273 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java @@ -0,0 +1,34 @@ +/** + * 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.transfer.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class TransferApiConstants { + + // general + public static final String localeParamName = "locale"; + public static final String dateFormatParamName = "dateFormat"; + + // request parameters + public static final String idParamName = "id"; + public static final String destinationGroupIdParamName = "destinationGroupId"; + public static final String clients = "clients"; + public static final String inheritDestinationGroupLoanOfficer = "inheritDestinationGroupLoanOfficer"; + public static final String newStaffIdParamName = "newStaffId"; + public static final String transferActiveLoans = "transferActiveLoans"; + public static final String destinationOfficeIdParamName = "destinationOfficeId"; + + public static final Set TRANSFER_CLIENTS_BETWEEN_GROUPS_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, + dateFormatParamName, destinationGroupIdParamName, clients, inheritDestinationGroupLoanOfficer, newStaffIdParamName, + transferActiveLoans)); + + public static final Set TRANSFER_CLIENT_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, + dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans)); + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java new file mode 100644 index 000000000..a71646c74 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java @@ -0,0 +1,111 @@ +/** + * 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.transfer.data; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.client.api.ClientApiConstants; +import org.mifosplatform.portfolio.group.api.GroupingTypesApiConstants; +import org.mifosplatform.portfolio.transfer.api.TransferApiConstants; +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 final class TransfersDataValidator { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public TransfersDataValidator(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + public void validateForClientsTransferBetweenGroups(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + TransferApiConstants.TRANSFER_CLIENTS_BETWEEN_GROUPS_DATA_PARAMETERS); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME); + + final Long destinationGroupId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.destinationGroupIdParamName, element); + baseDataValidator.reset().parameter(TransferApiConstants.destinationGroupIdParamName).value(destinationGroupId).notNull() + .integerGreaterThanZero(); + + if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.newStaffIdParamName, element)) { + final Long newStaffId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.newStaffIdParamName, element); + baseDataValidator.reset().parameter(TransferApiConstants.newStaffIdParamName).value(newStaffId).notNull() + .integerGreaterThanZero(); + } + + if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.transferActiveLoans, element)) { + final Boolean inheritDestinationGroupLoanOfficer = this.fromApiJsonHelper.extractBooleanNamed( + TransferApiConstants.transferActiveLoans, element); + baseDataValidator.reset().parameter(TransferApiConstants.transferActiveLoans).value(inheritDestinationGroupLoanOfficer) + .notNull(); + } + + if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.inheritDestinationGroupLoanOfficer, element)) { + final Boolean inheritDestinationGroupLoanOfficer = this.fromApiJsonHelper.extractBooleanNamed( + TransferApiConstants.inheritDestinationGroupLoanOfficer, element); + baseDataValidator.reset().parameter(TransferApiConstants.inheritDestinationGroupLoanOfficer) + .value(inheritDestinationGroupLoanOfficer).notNull(); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForClientTransferBetweenBranches(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + TransferApiConstants.TRANSFER_CLIENT_BETWEEN_BRANCHES_DATA_PARAMETERS); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(ClientApiConstants.CLIENT_RESOURCE_NAME); + + final Long destinationOfficeId = this.fromApiJsonHelper + .extractLongNamed(TransferApiConstants.destinationOfficeIdParamName, element); + baseDataValidator.reset().parameter(TransferApiConstants.destinationOfficeIdParamName).value(destinationOfficeId).notNull() + .integerGreaterThanZero(); + + if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.newStaffIdParamName, element)) { + final Long newStaffId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.newStaffIdParamName, element); + baseDataValidator.reset().parameter(TransferApiConstants.newStaffIdParamName).value(newStaffId).notNull() + .integerGreaterThanZero(); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/TransferNotSupportedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/TransferNotSupportedException.java new file mode 100644 index 000000000..a4959234d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/TransferNotSupportedException.java @@ -0,0 +1,51 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.transfer.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * {@link AbstractPlatformDomainRuleException} thrown when not supported loan + * template type is sent. + */ +public class TransferNotSupportedException extends AbstractPlatformDomainRuleException { + + /** + * Default Constructor (cannot transfer entities with active loan/savings + * accounts) + */ + public TransferNotSupportedException() { + super("error.msg.entity.transfers.with.active.accounts", "Cannot transfer Clients/Groups having active loan or savings accounts"); + } + + /** + * Overloaded Constructor (Cannot transfer Clients with active accounts + * between groups with different meeting frequency) + * + * @param clientId + * @param sourceGroupId + * @param destinationGroupId + */ + public TransferNotSupportedException(Long clientId, Long sourceGroupId, Long destinationGroupId) { + super("error.msg.client.transfers.with.active.accounts.between.groups.with.different.meeting.frequency", + "Cannot transfer Clients with active accounts between groups with different meeting frequency", clientId, sourceGroupId, + destinationGroupId); + } + + /** + * Overloaded Constructor (Cannot transfer Clients with active (JLG) + * accounts to a group with no meetings defined) + * + * @param clientId + * @param sourceGroupId + * @param destinationGroupId + */ + public TransferNotSupportedException(Long clientId, Long sourceGroupId, Long destinationGroupId, boolean destinationGroupMeetingAbsent) { + super("error.msg.client.transfers.with.active.accounts.to.group.with.no.meeting.frequency", + "Cannot transfer Client with active accounts to a groups with no meeting frequency", clientId, sourceGroupId, + destinationGroupId,destinationGroupMeetingAbsent); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientBetweenBranchesCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientBetweenBranchesCommandHandler.java new file mode 100644 index 000000000..5d2edea84 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientBetweenBranchesCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.transfer.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.transfer.service.TransferWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class TransferClientBetweenBranchesCommandHandler implements NewCommandSourceHandler { + + private final TransferWritePlatformService writePlatformService; + + @Autowired + public TransferClientBetweenBranchesCommandHandler(final TransferWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.transferClientBetweenBranches(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientsBetweenGroupsCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientsBetweenGroupsCommandHandler.java new file mode 100644 index 000000000..f8e79ace7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientsBetweenGroupsCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.transfer.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.transfer.service.TransferWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class TransferClientsBetweenGroupsCommandHandler implements NewCommandSourceHandler { + + private final TransferWritePlatformService writePlatformService; + + @Autowired + public TransferClientsBetweenGroupsCommandHandler(final TransferWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.transferClientsBetweenGroups(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformService.java new file mode 100644 index 000000000..6e11e6d46 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformService.java @@ -0,0 +1,17 @@ +/** + * 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.transfer.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public interface TransferWritePlatformService { + + CommandProcessingResult transferClientsBetweenGroups(final Long sourceGroupId, final JsonCommand jsonCommand); + + CommandProcessingResult transferClientBetweenBranches(final Long clientId, final JsonCommand jsonCommand); + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..4da7825a5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,260 @@ +/** + * 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.transfer.service; + +import java.util.ArrayList; +import java.util.List; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.organisation.office.domain.Office; +import org.mifosplatform.organisation.office.domain.OfficeRepositoryWrapper; +import org.mifosplatform.organisation.staff.domain.Staff; +import org.mifosplatform.portfolio.calendar.domain.Calendar; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; +import org.mifosplatform.portfolio.calendar.domain.CalendarType; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; +import org.mifosplatform.portfolio.client.exception.ClientHasBeenClosedException; +import org.mifosplatform.portfolio.group.domain.Group; +import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; +import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException; +import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.mifosplatform.portfolio.transfer.api.TransferApiConstants; +import org.mifosplatform.portfolio.transfer.data.TransfersDataValidator; +import org.mifosplatform.portfolio.transfer.exception.TransferNotSupportedException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +@Service +public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWritePlatformService { + + private final ClientRepositoryWrapper clientRepository; + private final OfficeRepositoryWrapper officeRepository; + private final CalendarInstanceRepository calendarInstanceRepository; + private final GroupRepositoryWrapper groupRepository; + private final LoanWritePlatformService loanWritePlatformService; + private final LoanRepository loanRepository; + private final TransfersDataValidator transfersDataValidator; + + @Autowired + public TransferWritePlatformServiceJpaRepositoryImpl(final ClientRepositoryWrapper clientRepository, + final OfficeRepositoryWrapper officeRepository, final CalendarInstanceRepository calendarInstanceRepository, + final LoanWritePlatformService loanWritePlatformService, final GroupRepositoryWrapper groupRepository, + final LoanRepository loanRepository, final TransfersDataValidator transfersDataValidator) { + this.clientRepository = clientRepository; + this.officeRepository = officeRepository; + this.calendarInstanceRepository = calendarInstanceRepository; + this.loanWritePlatformService = loanWritePlatformService; + this.groupRepository = groupRepository; + this.loanRepository = loanRepository; + this.transfersDataValidator = transfersDataValidator; + } + + @Override + @Transactional + public CommandProcessingResult transferClientsBetweenGroups(Long sourceGroupId, JsonCommand jsonCommand) { + this.transfersDataValidator.validateForClientsTransferBetweenGroups(jsonCommand.json()); + + final Group sourceGroup = this.groupRepository.findOneWithNotFoundDetection(sourceGroupId); + final Long destinationGroupId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationGroupIdParamName); + final Group destinationGroup = this.groupRepository.findOneWithNotFoundDetection(destinationGroupId); + + final List clients = this.assembleListOfClients(jsonCommand); + + for (Client client : clients) { + transferClientBetweenGroups(sourceGroup, client, destinationGroup); + } + + return new CommandProcessingResultBuilder() // + .withEntityId(sourceGroupId) // + .build(); + } + + /**** + * Variables that would make sense
+ *
    + *
  • inheritDestinationGroupLoanOfficer: Default False
  • + *
  • newStaffId: Optional field with Id of new Loan Officer to be linked + * to this client and all his JLG loans for this group
  • + *
  • transferActiveLoans:Default False, unless overridden, this client + * cannot be transferred between groups within the same branch if he has + * active JLG loans and across branches if he has any active loan
  • + *
  • + *
  • + *
  • + *
  • + *
  • + *
  • + *
  • + *
+ * ***/ + @Transactional + public void transferClientBetweenGroups(final Group sourceGroup, final Client client, final Group destinationGroup) { + + // next I shall validate that the client is present in this group + if (!sourceGroup.hasClientAsMember(client)) { throw new ClientNotInGroupException(client.getId(), sourceGroup.getId()); } + // Is client active? + if (client.isNotActive()) { throw new ClientHasBeenClosedException(client.getId()); } + + /*** Code for intra-branch transfers ***/ + if (client.getOffice().getId() == destinationGroup.getOffice().getId()) { + /** + * TODO: for now we need to ensure that only one collection sheet + * calendar can be linked with a center or group entity
+ **/ + final CalendarInstance sourceGroupCalendarInstance = this.calendarInstanceRepository + .findByEntityIdAndEntityTypeIdAndCalendarTypeId(sourceGroup.getId(), CalendarEntityType.GROUPS.getValue(), + CalendarType.COLLECTION.getValue()); + // get all customer loans synced with this group calendar Instance + final List activeLoanCalendarInstances = this.calendarInstanceRepository + .findCalendarInstancesForActiveLoansByGroupIdAndClientId(sourceGroup.getId(), client.getId()); + + /** + * if a calendar is present in the source group along with loans + * synced with it, we should ensure that the destination group also + * has a collection calendar + **/ + if (sourceGroupCalendarInstance != null && !activeLoanCalendarInstances.isEmpty()) { + // get the destination calendar + final CalendarInstance destinationGroupCalendarInstance = this.calendarInstanceRepository + .findByEntityIdAndEntityTypeIdAndCalendarTypeId(destinationGroup.getId(), CalendarEntityType.GROUPS.getValue(), + CalendarType.COLLECTION.getValue()); + + if (destinationGroupCalendarInstance == null) { throw new TransferNotSupportedException(client.getId(), + sourceGroup.getId(), destinationGroup.getId(), false); + + } + Calendar sourceGroupCalendar = destinationGroupCalendarInstance.getCalendar(); + Calendar destinationGroupCalendar = destinationGroupCalendarInstance.getCalendar(); + + /*** + * Ensure that the recurrence pattern are same for collection + * meeting in both the source and the destination calendar + ***/ + if (!(CalendarUtils.isFrequencySame(sourceGroupCalendar.getRecurrence(), destinationGroupCalendar.getRecurrence()) && CalendarUtils + .isIntervalSame(sourceGroupCalendar.getRecurrence(), destinationGroupCalendar.getRecurrence()))) { throw new TransferNotSupportedException( + client.getId(), sourceGroup.getId(), destinationGroup.getId()); } + + /** map all JLG loans for this client to the destinationGroup **/ + for (CalendarInstance calendarInstance : activeLoanCalendarInstances) { + calendarInstance.updateCalendar(destinationGroupCalendar); + } + // reschedule all JLG Loans to follow new Calendar + this.loanWritePlatformService.applyMeetingDateChanges(destinationGroupCalendar, activeLoanCalendarInstances); + } + + } else { + Office destinationOffice = destinationGroup.getOffice(); + transferClientToNewBranch(client, destinationOffice); + } + + /** + * Now Change the loan officer for this client and all his active JLG + * loans + **/ + Staff destinationGroupLoanOfficer = destinationGroup.getStaff(); + if (destinationGroupLoanOfficer != null) { + client.updateStaff(destinationGroupLoanOfficer); + } + + client.getGroups().add(destinationGroup); + this.clientRepository.saveAndFlush(client); + + /** + * Active JLG loans are now linked to the new Group and Loan officer + **/ + List allClientJLGLoans = loanRepository.findByClientIdAndGroupId(client.getId(), sourceGroup.getId()); + for (Loan loan : allClientJLGLoans) { + if (loan.status().isActiveOrAwaitingApprovalOrDisbursal()) { + loan.updateGroup(destinationGroup); + if (destinationGroupLoanOfficer != null) { + loan.reassignLoanOfficer(destinationGroupLoanOfficer, DateUtils.getLocalDateOfTenant()); + } + this.loanRepository.saveAndFlush(loan); + } + } + + // change client group membership + client.getGroups().remove(sourceGroup); + + } + + /** + * This API is meant for transferring clients between branches mainly by + * Organizations following an Individual lending Model
+ * If the Client is linked to any Groups, we can optionally choose to have + * all the linkages broken and all JLG Loans are converted into Individual + * Loans + * + * @param clientId + * @param destinationOfficeId + * @param jsonCommand + * @return + **/ + @Transactional + @Override + public CommandProcessingResult transferClientBetweenBranches(final Long clientId, final JsonCommand jsonCommand) { + // validation + this.transfersDataValidator.validateForClientTransferBetweenBranches(jsonCommand.json()); + + final Long destinationOfficeId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationOfficeIdParamName); + final Office office = this.officeRepository.findOneWithNotFoundDetection(destinationOfficeId); + final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); + /*** Remove all group associations for this client ***/ + client.getGroups().clear(); + transferClientToNewBranch(client, office); + this.clientRepository.save(client); + + return new CommandProcessingResultBuilder() // + .withClientId(clientId) // + .withEntityId(clientId) // + .build(); + } + + private void transferClientToNewBranch(final Client client, final Office destinationOffice) { + /** + * For now, do not allow Clients with active loans to be transferred + * between branches + **/ + if (loanRepository.doesClientHaveActiveLoans(client.getId())) { throw new TransferNotSupportedException(); } + // set the new office for the client + client.updateOffice(destinationOffice); + } + + private List assembleListOfClients(final JsonCommand command) { + + final List clients = new ArrayList(); + + if (command.parameterExists(TransferApiConstants.clients)) { + JsonArray clientsArray = command.arrayOfParameterNamed(TransferApiConstants.clients); + if (clientsArray != null) { + for (int i = 0; i < clientsArray.size(); i++) { + + final JsonObject jsonObject = clientsArray.get(i).getAsJsonObject(); + if (jsonObject.has(TransferApiConstants.idParamName)) { + final Long id = jsonObject.get(TransferApiConstants.idParamName).getAsLong(); + final Client client = this.clientRepository.findOneWithNotFoundDetection(id); + clients.add(client); + } + } + } + } + return clients; + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java index fa1a18743..26feeae57 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java @@ -23,7 +23,6 @@ import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.persistence.UniqueConstraint; -import org.apache.commons.lang.StringUtils; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.security.domain.PlatformUser; @@ -43,443 +42,491 @@ import org.springframework.security.core.userdetails.User; @Table(name = "m_appuser", uniqueConstraints = @UniqueConstraint(columnNames = { "username" }, name = "username_org")) public class AppUser extends AbstractPersistable implements PlatformUser { - private final static Logger logger = LoggerFactory.getLogger(AppUser.class); - - @Column(name = "email", nullable = false, length = 100) - private String email; - - @Column(name = "username", nullable = false, length = 100) - private String username; - - @Column(name = "firstname", nullable = false, length = 100) - private String firstname; - - @Column(name = "lastname", nullable = false, length = 100) - private String lastname; - - @Column(name = "password", nullable = false) - private String password; - - @Column(name = "nonexpired", nullable = false) - private boolean accountNonExpired; - - @Column(name = "nonlocked", nullable = false) - private final boolean accountNonLocked; - - @Column(name = "nonexpired_credentials", nullable = false) - private final boolean credentialsNonExpired; - - @Column(name = "enabled", nullable = false) - private boolean enabled; - - @SuppressWarnings("unused") - @Column(name = "firsttime_login_remaining", nullable = false) - private boolean firstTimeLoginRemaining; - - @Column(name = "is_deleted", nullable = false) - private boolean deleted; - - @ManyToOne - @JoinColumn(name = "office_id", nullable = false) - private Office office; - - @ManyToOne - @JoinColumn(name = "staff_id", nullable = true) - private Staff staff; - - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "m_appuser_role", joinColumns = @JoinColumn(name = "appuser_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) - private Set roles; - - public static AppUser fromJson(final Office userOffice, final Set allRoles, final JsonCommand command) { - - final String username = command.stringValueOfParameterNamed("username"); - String password = command.stringValueOfParameterNamed("password"); - if (StringUtils.isBlank(password)) { - password = new RandomPasswordGenerator(13).generate(); - } - - boolean userEnabled = true; - boolean userAccountNonExpired = true; - boolean userCredentialsNonExpired = true; - boolean userAccountNonLocked = true; - - Collection authorities = new ArrayList(); - authorities.add(new SimpleGrantedAuthority("DUMMY_ROLE_NOT_USED_OR_PERSISTED_TO_AVOID_EXCEPTION")); - - User user = new User(username, password, userEnabled, userAccountNonExpired, userCredentialsNonExpired, userAccountNonLocked, - authorities); - - final String email = command.stringValueOfParameterNamed("email"); - final String firstname = command.stringValueOfParameterNamed("firstname"); - final String lastname = command.stringValueOfParameterNamed("lastname"); - - return new AppUser(userOffice, user, allRoles, email, firstname, lastname); - } - - protected AppUser() { - this.accountNonLocked = false; - this.credentialsNonExpired = false; - this.roles = new HashSet(); - } - - public AppUser(final Office office, final User user, final Set roles, final String email, final String firstname, - final String lastname) { - this.office = office; - this.email = email.trim(); - this.username = user.getUsername().trim(); - this.firstname = firstname.trim(); - this.lastname = lastname.trim(); - this.password = user.getPassword().trim(); - this.accountNonExpired = user.isAccountNonExpired(); - this.accountNonLocked = user.isAccountNonLocked(); - this.credentialsNonExpired = user.isCredentialsNonExpired(); - this.enabled = user.isEnabled(); - this.roles = roles; - this.firstTimeLoginRemaining = true; - } - - public EnumOptionData organisationalRoleData() { - EnumOptionData organisationalRole = null; - if (this.staff != null) { - organisationalRole = this.staff.organisationalRoleData(); - } - return organisationalRole; - } - - public void updatePassword(final String encodePassword) { - this.password = encodePassword; - this.firstTimeLoginRemaining = false; - } - - public void changeOffice(final Office differentOffice) { - this.office = differentOffice; - } - - public void updateRoles(final Set allRoles) { - if (!allRoles.isEmpty()) { - this.roles.clear(); - this.roles = allRoles; - } - } - - public Map update(final JsonCommand command, final PlatformPasswordEncoder platformPasswordEncoder) { - - final Map actualChanges = new LinkedHashMap(7); - - // unencoded password provided - final String passwordParamName = "password"; - final String passwordEncodedParamName = "passwordEncoded"; - if (command.hasParameter(passwordParamName)) { - if (command.isChangeInPasswordParameterNamed(passwordParamName, this.password, platformPasswordEncoder, this.getId())) { - final String passwordEncodedValue = command.passwordValueOfParameterNamed(passwordParamName, platformPasswordEncoder, - this.getId()); - actualChanges.put(passwordEncodedParamName, passwordEncodedValue); - updatePassword(passwordEncodedValue); - } - } - - if (command.hasParameter(passwordEncodedParamName)) { - if (command.isChangeInStringParameterNamed(passwordEncodedParamName, this.password)) { - final String newValue = command.stringValueOfParameterNamed(passwordEncodedParamName); - actualChanges.put(passwordEncodedParamName, newValue); - updatePassword(newValue); - } - } - - final String officeIdParamName = "officeId"; - if (command.isChangeInLongParameterNamed(officeIdParamName, this.office.getId())) { - final Long newValue = command.longValueOfParameterNamed(officeIdParamName); - actualChanges.put(officeIdParamName, newValue); - } - - final String rolesParamName = "roles"; - if (command.isChangeInArrayParameterNamed(rolesParamName, getRolesAsIdStringArray())) { - final String[] newValue = command.arrayValueOfParameterNamed(rolesParamName); - actualChanges.put(rolesParamName, newValue); - } - - final String usernameParamName = "username"; - if (command.isChangeInStringParameterNamed(usernameParamName, this.username)) { - final String newValue = command.stringValueOfParameterNamed(usernameParamName); - actualChanges.put(usernameParamName, newValue); - this.username = newValue; - } - - final String firstnameParamName = "firstname"; - if (command.isChangeInStringParameterNamed(firstnameParamName, this.firstname)) { - final String newValue = command.stringValueOfParameterNamed(firstnameParamName); - actualChanges.put(firstnameParamName, newValue); - this.firstname = newValue; - } - - final String lastnameParamName = "lastname"; - if (command.isChangeInStringParameterNamed(lastnameParamName, this.lastname)) { - final String newValue = command.stringValueOfParameterNamed(lastnameParamName); - actualChanges.put(lastnameParamName, newValue); - this.lastname = newValue; - } - - final String emailParamName = "email"; - if (command.isChangeInStringParameterNamed(emailParamName, this.email)) { - final String newValue = command.stringValueOfParameterNamed(emailParamName); - actualChanges.put(emailParamName, newValue); - this.email = newValue; - } - - return actualChanges; - } - - private String[] getRolesAsIdStringArray() { - List roleIds = new ArrayList(); - - for (Role role : this.roles) { - roleIds.add(role.getId().toString()); - } - - return roleIds.toArray(new String[roleIds.size()]); - } - - /** - * Delete is a soft delete. Updates flag so it wont appear in - * query/report results. - * - * Any fields with unique constraints and prepended with id of record. - */ - public void delete() { - this.deleted = true; - this.enabled = false; - this.accountNonExpired = false; - this.firstTimeLoginRemaining = true; - this.username = this.getId() + "_DELETED_" + this.username; - } - - public boolean isDeleted() { - return deleted; - } - - @Override - public Collection getAuthorities() { - return this.populateGrantedAuthorities(); - } - - private List populateGrantedAuthorities() { - List grantedAuthorities = new ArrayList(); - for (Role role : this.roles) { - Collection permissions = role.getPermissions(); - for (Permission permission : permissions) { - grantedAuthorities.add(new SimpleGrantedAuthority(permission.getCode())); - } - } - return grantedAuthorities; - } - - @Override - public String getPassword() { - return this.password; - } - - @Override - public String getUsername() { - return this.username; - } - - @Override - public boolean isAccountNonExpired() { - return this.accountNonExpired; - } - - @Override - public boolean isAccountNonLocked() { - return this.accountNonLocked; - } - - @Override - public boolean isCredentialsNonExpired() { - return this.credentialsNonExpired; - } - - @Override - public boolean isEnabled() { - return this.enabled; - } - - public String getFirstname() { - return firstname; - } - - public String getLastname() { - return lastname; - } - - public String getEmail() { - return this.email; - } - - public Set getRoles() { - return this.roles; - } - - public Office getOffice() { - return this.office; - } - - public boolean canNotApproveLoanInPast() { - return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "APPROVEINPAST_LOAN"); - } - - public boolean canNotRejectLoanInPast() { - return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "REJECTINPAST_LOAN"); - } - - public boolean canNotWithdrawByClientLoanInPast() { - return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "WITHDRAWINPAST_LOAN"); - } - - public boolean canNotDisburseLoanInPast() { - return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "DISBURSEINPAST_LOAN"); - } - - public boolean canNotMakeRepaymentOnLoanInPast() { - return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "REPAYMENTINPAST_LOAN"); - } - - public boolean hasNotPermissionForReport(final String reportName) { - - if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "REPORTING_SUPER_USER", "READ_" + reportName)) return true; - - return false; - } - - public boolean hasNotPermissionForDatatable(final String datatable, final String accessType) { - - String matchPermission = accessType + "_" + datatable; - - if (accessType.equalsIgnoreCase("READ")) { - - if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", matchPermission)) return true; - - return false; - } - - if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", matchPermission)) return true; - - return false; - } - - public boolean hasNotPermissionForAnyOf(final String... permissionCodes) { - boolean hasNotPermission = true; - for (String permissionCode : permissionCodes) { - boolean checkPermission = this.hasPermissionTo(permissionCode); - if (checkPermission) { - hasNotPermission = false; - break; - } - } - return hasNotPermission; - } - - public void validateHasReadPermission(final String resourceType) { - - final String authorizationMessage = "User has no authority to view " + resourceType.toLowerCase() + "s"; - final String matchPermission = "READ_" + resourceType.toUpperCase(); - - if (!(hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", matchPermission))) return; - - throw new NoAuthorizationException(authorizationMessage); - } - - private boolean hasNotPermissionTo(final String permissionCode) { - return !hasPermissionTo(permissionCode); - } - - private boolean hasPermissionTo(final String permissionCode) { - boolean hasPermission = hasAllFunctionsPermission(); - if (!hasPermission) { - for (Role role : this.roles) { - if (role.hasPermissionTo(permissionCode)) { - hasPermission = true; - break; - } - } - } - return hasPermission; - } - - private boolean hasAllFunctionsPermission() { - boolean match = false; - for (Role role : this.roles) { - if (role.hasPermissionTo("ALL_FUNCTIONS")) { - match = true; - break; - } - } - return match; - } - - public boolean hasIdOf(final Long userId) { - return getId().equals(userId); - } - - private boolean hasNotAnyPermission(final List permissions) { - return !hasAnyPermission(permissions); - } - - private boolean hasAnyPermission(final List permissions) { - boolean hasAtLeastOneOf = false; - - for (final String permissionCode : permissions) { - if (hasPermissionTo(permissionCode)) { - hasAtLeastOneOf = true; - break; - } - } - - return hasAtLeastOneOf; - } - - public void validateHasPermissionTo(final String function, final List allowedPermissions) { - if (hasNotAnyPermission(allowedPermissions)) { - final String authorizationMessage = "User has no authority to: " + function; - throw new NoAuthorizationException(authorizationMessage); - } - } - - public void validateHasPermissionTo(final String function) { - if (hasNotPermissionTo(function)) { - final String authorizationMessage = "User has no authority to: " + function; - logger.info("Unauthorized access: userId: " + this.getId() + " action: " + function + " allowed: " + getAuthorities()); - throw new NoAuthorizationException(authorizationMessage); - } - } - - public void validateHasCheckerPermissionTo(final String function) { - - final String checkerPermissionName = function.toUpperCase() + "_CHECKER"; - if (hasNotPermissionTo("CHECKER_SUPER_USER") && hasNotPermissionTo(checkerPermissionName)) { - final String authorizationMessage = "User has no authority to be a checker for: " + function; - throw new NoAuthorizationException(authorizationMessage); - } - } - - public void validateHasDatatableReadPermission(final String datatable) { - if (hasNotPermissionForDatatable(datatable, "READ")) { throw new NoAuthorizationException("Not authorised to read datatable: " - + datatable); } - } - - public Long getStaffId() { - Long staffId = null; - if (this.staff != null) { - staffId = this.staff.getId(); - } - return staffId; - } - - public String getStaffDisplayName() { - String staffDisplayName = null; - if (this.staff != null) { - staffDisplayName = this.staff.displayName(); - } - return staffDisplayName; - } + private final static Logger logger = LoggerFactory.getLogger(AppUser.class); + + @Column(name = "email", nullable = false, length = 100) + private String email; + + @Column(name = "username", nullable = false, length = 100) + private String username; + + @Column(name = "firstname", nullable = false, length = 100) + private String firstname; + + @Column(name = "lastname", nullable = false, length = 100) + private String lastname; + + @Column(name = "password", nullable = false) + private String password; + + @Column(name = "nonexpired", nullable = false) + private boolean accountNonExpired; + + @Column(name = "nonlocked", nullable = false) + private final boolean accountNonLocked; + + @Column(name = "nonexpired_credentials", nullable = false) + private final boolean credentialsNonExpired; + + @Column(name = "enabled", nullable = false) + private boolean enabled; + + @Column(name = "firsttime_login_remaining", nullable = false) + private boolean firstTimeLoginRemaining; + + @Column(name = "is_deleted", nullable = false) + private boolean deleted; + + @ManyToOne + @JoinColumn(name = "office_id", nullable = false) + private Office office; + + @ManyToOne + @JoinColumn(name = "staff_id", nullable = true) + private Staff staff; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "m_appuser_role", joinColumns = @JoinColumn(name = "appuser_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) + private Set roles; + + public static AppUser fromJson(final Office userOffice, + final Set allRoles, final JsonCommand command) { + + final String username = command.stringValueOfParameterNamed("username"); + String password = command.stringValueOfParameterNamed("password"); + final Boolean sendPasswordToEmail = command + .booleanObjectValueOfParameterNamed("sendPasswordToEmail"); + + if (sendPasswordToEmail.booleanValue()) { + password = new RandomPasswordGenerator(13).generate(); + } + + final boolean userEnabled = true; + final boolean userAccountNonExpired = true; + final boolean userCredentialsNonExpired = true; + final boolean userAccountNonLocked = true; + + final Collection authorities = new ArrayList(); + authorities.add(new SimpleGrantedAuthority( + "DUMMY_ROLE_NOT_USED_OR_PERSISTED_TO_AVOID_EXCEPTION")); + + final User user = new User(username, password, userEnabled, + userAccountNonExpired, userCredentialsNonExpired, + userAccountNonLocked, authorities); + + final String email = command.stringValueOfParameterNamed("email"); + final String firstname = command + .stringValueOfParameterNamed("firstname"); + final String lastname = command.stringValueOfParameterNamed("lastname"); + + return new AppUser(userOffice, user, allRoles, email, firstname, + lastname); + } + + protected AppUser() { + this.accountNonLocked = false; + this.credentialsNonExpired = false; + this.roles = new HashSet(); + } + + public AppUser(final Office office, final User user, final Set roles, + final String email, final String firstname, final String lastname) { + this.office = office; + this.email = email.trim(); + this.username = user.getUsername().trim(); + this.firstname = firstname.trim(); + this.lastname = lastname.trim(); + this.password = user.getPassword().trim(); + this.accountNonExpired = user.isAccountNonExpired(); + this.accountNonLocked = user.isAccountNonLocked(); + this.credentialsNonExpired = user.isCredentialsNonExpired(); + this.enabled = user.isEnabled(); + this.roles = roles; + this.firstTimeLoginRemaining = true; + } + + public EnumOptionData organisationalRoleData() { + EnumOptionData organisationalRole = null; + if (this.staff != null) { + organisationalRole = this.staff.organisationalRoleData(); + } + return organisationalRole; + } + + public void updatePassword(final String encodePassword) { + this.password = encodePassword; + this.firstTimeLoginRemaining = false; + } + + public void changeOffice(final Office differentOffice) { + this.office = differentOffice; + } + + public void updateRoles(final Set allRoles) { + if (!allRoles.isEmpty()) { + this.roles.clear(); + this.roles = allRoles; + } + } + + public Map update(final JsonCommand command, + final PlatformPasswordEncoder platformPasswordEncoder) { + + final Map actualChanges = new LinkedHashMap( + 7); + + // unencoded password provided + final String passwordParamName = "password"; + final String passwordEncodedParamName = "passwordEncoded"; + if (command.hasParameter(passwordParamName)) { + if (command.isChangeInPasswordParameterNamed(passwordParamName, + this.password, platformPasswordEncoder, getId())) { + final String passwordEncodedValue = command + .passwordValueOfParameterNamed(passwordParamName, + platformPasswordEncoder, getId()); + actualChanges.put(passwordEncodedParamName, + passwordEncodedValue); + updatePassword(passwordEncodedValue); + } + } + + if (command.hasParameter(passwordEncodedParamName)) { + if (command.isChangeInStringParameterNamed( + passwordEncodedParamName, this.password)) { + final String newValue = command + .stringValueOfParameterNamed(passwordEncodedParamName); + actualChanges.put(passwordEncodedParamName, newValue); + updatePassword(newValue); + } + } + + final String officeIdParamName = "officeId"; + if (command.isChangeInLongParameterNamed(officeIdParamName, + this.office.getId())) { + final Long newValue = command + .longValueOfParameterNamed(officeIdParamName); + actualChanges.put(officeIdParamName, newValue); + } + + final String rolesParamName = "roles"; + if (command.isChangeInArrayParameterNamed(rolesParamName, + getRolesAsIdStringArray())) { + final String[] newValue = command + .arrayValueOfParameterNamed(rolesParamName); + actualChanges.put(rolesParamName, newValue); + } + + final String usernameParamName = "username"; + if (command.isChangeInStringParameterNamed(usernameParamName, + this.username)) { + final String newValue = command + .stringValueOfParameterNamed(usernameParamName); + actualChanges.put(usernameParamName, newValue); + this.username = newValue; + } + + final String firstnameParamName = "firstname"; + if (command.isChangeInStringParameterNamed(firstnameParamName, + this.firstname)) { + final String newValue = command + .stringValueOfParameterNamed(firstnameParamName); + actualChanges.put(firstnameParamName, newValue); + this.firstname = newValue; + } + + final String lastnameParamName = "lastname"; + if (command.isChangeInStringParameterNamed(lastnameParamName, + this.lastname)) { + final String newValue = command + .stringValueOfParameterNamed(lastnameParamName); + actualChanges.put(lastnameParamName, newValue); + this.lastname = newValue; + } + + final String emailParamName = "email"; + if (command.isChangeInStringParameterNamed(emailParamName, this.email)) { + final String newValue = command + .stringValueOfParameterNamed(emailParamName); + actualChanges.put(emailParamName, newValue); + this.email = newValue; + } + + return actualChanges; + } + + private String[] getRolesAsIdStringArray() { + final List roleIds = new ArrayList(); + + for (final Role role : this.roles) { + roleIds.add(role.getId().toString()); + } + + return roleIds.toArray(new String[roleIds.size()]); + } + + /** + * Delete is a soft delete. Updates flag so it wont appear in + * query/report results. + * + * Any fields with unique constraints and prepended with id of record. + */ + public void delete() { + this.deleted = true; + this.enabled = false; + this.accountNonExpired = false; + this.firstTimeLoginRemaining = true; + this.username = getId() + "_DELETED_" + this.username; + } + + public boolean isDeleted() { + return this.deleted; + } + + @Override + public Collection getAuthorities() { + return populateGrantedAuthorities(); + } + + private List populateGrantedAuthorities() { + final List grantedAuthorities = new ArrayList(); + for (final Role role : this.roles) { + final Collection permissions = role.getPermissions(); + for (final Permission permission : permissions) { + grantedAuthorities.add(new SimpleGrantedAuthority(permission + .getCode())); + } + } + return grantedAuthorities; + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isAccountNonExpired() { + return this.accountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return this.accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return this.credentialsNonExpired; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + + public String getFirstname() { + return this.firstname; + } + + public String getLastname() { + return this.lastname; + } + + public String getEmail() { + return this.email; + } + + public Set getRoles() { + return this.roles; + } + + public Office getOffice() { + return this.office; + } + + public boolean canNotApproveLoanInPast() { + return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "APPROVEINPAST_LOAN"); + } + + public boolean canNotRejectLoanInPast() { + return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "REJECTINPAST_LOAN"); + } + + public boolean canNotWithdrawByClientLoanInPast() { + return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "WITHDRAWINPAST_LOAN"); + } + + public boolean canNotDisburseLoanInPast() { + return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "DISBURSEINPAST_LOAN"); + } + + public boolean canNotMakeRepaymentOnLoanInPast() { + return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "REPAYMENTINPAST_LOAN"); + } + + public boolean hasNotPermissionForReport(final String reportName) { + + if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", + "REPORTING_SUPER_USER", "READ_" + reportName)) { + return true; + } + + return false; + } + + public boolean hasNotPermissionForDatatable(final String datatable, + final String accessType) { + + final String matchPermission = accessType + "_" + datatable; + + if (accessType.equalsIgnoreCase("READ")) { + + if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", + matchPermission)) { + return true; + } + + return false; + } + + if (hasNotPermissionForAnyOf("ALL_FUNCTIONS", matchPermission)) { + return true; + } + + return false; + } + + public boolean hasNotPermissionForAnyOf(final String... permissionCodes) { + boolean hasNotPermission = true; + for (final String permissionCode : permissionCodes) { + final boolean checkPermission = hasPermissionTo(permissionCode); + if (checkPermission) { + hasNotPermission = false; + break; + } + } + return hasNotPermission; + } + + public void validateHasReadPermission(final String resourceType) { + + final String authorizationMessage = "User has no authority to view " + + resourceType.toLowerCase() + "s"; + final String matchPermission = "READ_" + resourceType.toUpperCase(); + + if (!hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", + matchPermission)) { + return; + } + + throw new NoAuthorizationException(authorizationMessage); + } + + private boolean hasNotPermissionTo(final String permissionCode) { + return !hasPermissionTo(permissionCode); + } + + private boolean hasPermissionTo(final String permissionCode) { + boolean hasPermission = hasAllFunctionsPermission(); + if (!hasPermission) { + for (final Role role : this.roles) { + if (role.hasPermissionTo(permissionCode)) { + hasPermission = true; + break; + } + } + } + return hasPermission; + } + + private boolean hasAllFunctionsPermission() { + boolean match = false; + for (final Role role : this.roles) { + if (role.hasPermissionTo("ALL_FUNCTIONS")) { + match = true; + break; + } + } + return match; + } + + public boolean hasIdOf(final Long userId) { + return getId().equals(userId); + } + + private boolean hasNotAnyPermission(final List permissions) { + return !hasAnyPermission(permissions); + } + + private boolean hasAnyPermission(final List permissions) { + boolean hasAtLeastOneOf = false; + + for (final String permissionCode : permissions) { + if (hasPermissionTo(permissionCode)) { + hasAtLeastOneOf = true; + break; + } + } + + return hasAtLeastOneOf; + } + + public void validateHasPermissionTo(final String function, + final List allowedPermissions) { + if (hasNotAnyPermission(allowedPermissions)) { + final String authorizationMessage = "User has no authority to: " + + function; + throw new NoAuthorizationException(authorizationMessage); + } + } + + public void validateHasPermissionTo(final String function) { + if (hasNotPermissionTo(function)) { + final String authorizationMessage = "User has no authority to: " + + function; + logger.info("Unauthorized access: userId: " + getId() + " action: " + + function + " allowed: " + getAuthorities()); + throw new NoAuthorizationException(authorizationMessage); + } + } + + public void validateHasCheckerPermissionTo(final String function) { + + final String checkerPermissionName = function.toUpperCase() + + "_CHECKER"; + if (hasNotPermissionTo("CHECKER_SUPER_USER") + && hasNotPermissionTo(checkerPermissionName)) { + final String authorizationMessage = "User has no authority to be a checker for: " + + function; + throw new NoAuthorizationException(authorizationMessage); + } + } + + public void validateHasDatatableReadPermission(final String datatable) { + if (hasNotPermissionForDatatable(datatable, "READ")) { + throw new NoAuthorizationException( + "Not authorised to read datatable: " + datatable); + } + } + + public Long getStaffId() { + Long staffId = null; + if (this.staff != null) { + staffId = this.staff.getId(); + } + return staffId; + } + + public String getStaffDisplayName() { + String staffDisplayName = null; + if (this.staff != null) { + staffDisplayName = this.staff.displayName(); + } + return staffDisplayName; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/Permission.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/Permission.java index 84855dbfb..aedfd1e77 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/Permission.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/Permission.java @@ -21,11 +21,9 @@ public class Permission extends AbstractPersistable { @Column(name = "code", nullable = false, length = 100) private final String code; - @SuppressWarnings("unused") @Column(name = "entity_name", nullable = true, length = 100) private final String entityName; - @SuppressWarnings("unused") @Column(name = "action_name", nullable = true, length = 100) private final String actionName; @@ -66,7 +64,7 @@ public class Permission extends AbstractPersistable { } public boolean enableMakerChecker(final boolean canMakerChecker) { - final boolean isUpdatedValueSame = (this.canMakerChecker == canMakerChecker); + final boolean isUpdatedValueSame = this.canMakerChecker == canMakerChecker; this.canMakerChecker = canMakerChecker; return !isUpdatedValueSame; diff --git a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MonetaryCurrencyBuilder.java b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MonetaryCurrencyBuilder.java index 1a4a26e5b..9c5e25b23 100644 --- a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MonetaryCurrencyBuilder.java +++ b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MonetaryCurrencyBuilder.java @@ -6,9 +6,10 @@ public class MonetaryCurrencyBuilder { private String code = "XOF"; private int digitsAfterDecimal = 0; + private Integer inMultiplesOf = null; public MonetaryCurrency build() { - return new MonetaryCurrency(code, digitsAfterDecimal); + return new MonetaryCurrency(code, digitsAfterDecimal,inMultiplesOf); } public MonetaryCurrencyBuilder withCode(final String withCode) {